diff --git a/CMakeLists.txt b/CMakeLists.txt index ddeebbd96d..13d316420a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -361,6 +361,8 @@ set(BOUT_SOURCES ./src/mesh/g_values.cxx ${CMAKE_CURRENT_BINARY_DIR}/include/bout/revision.hxx ${CMAKE_CURRENT_BINARY_DIR}/include/bout/version.hxx + ./include/bout/tokamak_coordinates.hxx + ./src/mesh/tokamak_coordinates.cxx ) diff --git a/examples/6field-simple/elm_6f.cxx b/examples/6field-simple/elm_6f.cxx index a38a1420e2..88f2830d99 100644 --- a/examples/6field-simple/elm_6f.cxx +++ b/examples/6field-simple/elm_6f.cxx @@ -6,7 +6,6 @@ * T. Xia *******************************************************************************/ -#include "bout/bout.hxx" #include "bout/constants.hxx" #include "bout/derivs.hxx" #include "bout/initialprofiles.hxx" @@ -16,6 +15,7 @@ #include "bout/msg_stack.hxx" #include "bout/physicsmodel.hxx" #include "bout/sourcex.hxx" +#include "bout/tokamak_coordinates.hxx" #include @@ -23,1656 +23,1627 @@ constexpr BoutReal eV_K = 11605.0; // 1eV = 11605K class Elm_6f : public PhysicsModel { - /// The total height, average width and center of profile of N0 - BoutReal n0_height, n0_ave, n0_width, n0_center, n0_bottom_x; - /// The ampitude of congstant temperature - BoutReal Tconst; - - /// Test the effect of first order term of invert Laplace function - BoutReal laplace_alpha; - /// The ratio of Ti0/Te0 - BoutReal Tau_ie; - - // 2D inital profiles - /// Current and pressure - Field2D J0, P0; - /// Curvature term - Vector2D b0xcv; - /// When diamagnetic terms used - Field2D phi0; - - /// Number density and temperature for ions and electrons - Field2D N0, Ti0, Te0, Ne0; - Field2D Pi0, Pe0; - Field2D q95; - BoutReal q95_input; - bool local_q; - BoutReal q_alpha; - bool n0_fake_prof, T0_fake_prof; - /// Charge number of ion - BoutReal Zi; - - /// B0 field vector - Vector2D B0vec; - - // V0 field vectors - /// V0 field vector in convection - Vector2D V0vec; - /// Effective V0 field vector in Ohm's law - Vector2D V0eff; - - // 3D evolving variables - Field3D U, Psi, P, Pi, Pe; - Field3D Ni, Te, Ti, Ne; - Field3D Vipar, Vepar; - - // Derived 3D variables - Field3D Jpar, phi; // Parallel current, electric potential - - Field3D Ajpar; // Parallel current, electric potential - bool emass; - BoutReal emass_inv; // inverse of electron mass - BoutReal coef_jpar; - BoutReal delta_e; // Normalized electron skin depth - BoutReal delta_e_inv; // inverse normalized electron skin depth - BoutReal gyroAlv; // Normalized ion current coef - Field3D ubyn; - - Field3D Jpar2; // Delp2 of Parallel current - Field3D tmpA2; // Grad2_par2new of Parallel vector potential - Field3D tmpN2, tmpTi2, tmpTe2, tmpVp2; // Grad2_par2new of Parallel density - - // Constraint - Field3D C_phi; - - // Parameters - BoutReal density; // Number density [m^-3] - BoutReal Bbar, Lbar, Tbar, Va; // Normalisation constants - BoutReal Nbar, Tibar, Tebar; - BoutReal dia_fact; // Multiply diamagnetic term by this - - BoutReal diffusion_par; // Parallel thermal conductivity - BoutReal diffusion_perp; // Perpendicular thermal conductivity (>0 open) - BoutReal diffusion_n4, diffusion_ti4, - diffusion_te4; // M: 4th Parallel density diffusion - BoutReal diffusion_v4; - BoutReal diffusion_u4; // xqx: parallel hyper-viscous diffusion for vorticity - - BoutReal heating_P; // heating power in pressure - BoutReal hp_width; // heating profile radial width in pressure - BoutReal hp_length; // heating radial domain in pressure - BoutReal sink_vp; // sink in pressure - BoutReal sp_width; // sink profile radial width in pressure - BoutReal sp_length; // sink radial domain in pressure - - BoutReal sink_Ul; // left edge sink in vorticity - BoutReal su_widthl; // left edge sink profile radial width in vorticity - BoutReal su_lengthl; // left edge sink radial domain in vorticity - - BoutReal sink_Ur; // right edge sink in vorticity - BoutReal su_widthr; // right edge sink profile radial width in vorticity - BoutReal su_lengthr; // right edge sink radial domain in vorticity - - BoutReal viscos_par; // Parallel viscosity - BoutReal viscos_perp; // Perpendicular viscosity - BoutReal hyperviscos; // Hyper-viscosity (radial) - Field3D hyper_mu_x; // Hyper-viscosity coefficient - - Field3D Dperp2Phi0, Dperp2Phi, GradPhi02, - GradPhi2; // Temporary variables for gyroviscous - Field3D GradparPhi02, GradparPhi2, GradcPhi, GradcparPhi; - Field3D Dperp2Pi0, Dperp2Pi, bracketPhi0P, bracketPhiP0, bracketPhiP; - - BoutReal Psipara1, Upara0, - Upara1; // Temporary normalization constants for all the equations - BoutReal Upara2, Upara3, Nipara1; - BoutReal Tipara1, Tipara2; - BoutReal Tepara1, Tepara2, Tepara3, Tepara4; - BoutReal Vepara, Vipara; - BoutReal Low_limit; // To limit the negative value of total density and temperatures - - Field3D Te_tmp, Ti_tmp, N_tmp; // to avoid the negative value of total value - BoutReal gamma_i_BC, gamma_e_BC; // sheath energy transmission factors - int Sheath_width; - Field3D c_se, Jpar_sh, q_se, q_si, vth_et, - c_set; // variables for sheath boundary conditions - Field2D vth_e0, c_se0, Jpar_sh0; - BoutReal const_cse; - - // options - bool include_curvature, include_jpar0, compress0; - bool evolve_pressure, continuity, gyroviscous; - Field3D diff_radial, ddx_ni, ddx_n0; - BoutReal diffusion_coef_Hmode0, diffusion_coef_Hmode1; - Field3D eta_i0, pi_ci; - - BoutReal vacuum_pressure; - BoutReal vacuum_trans; // Transition width - Field3D vac_mask; - - bool nonlinear; - bool evolve_jpar; - BoutReal g; // Only if compressible - bool phi_curv; - - // Poisson brackets: b0 x Grad(f) dot Grad(g) / B = [f, g] - // Method to use: BRACKET_ARAKAWA, BRACKET_STD or BRACKET_SIMPLE - BRACKET_METHOD bm_exb, bm_mag; // Bracket method for advection terms - int bracket_method_exb, bracket_method_mag; - - bool diamag; - bool energy_flux, energy_exch; // energy flux term - bool diamag_phi0; // Include the diamagnetic equilibrium phi0 - bool thermal_force; // Include the thermal flux term in Ohm's law - bool eHall; - BoutReal AA; // ion mass in units of the proton mass; AA=Mi/Mp - - BoutReal Vt0; // equilibrium toroidal flow normalized to Alfven velocity - BoutReal Vp0; // equilibrium poloidal flow normalized to Alfven velocity - - bool experiment_Er; // read in phi_0 from experiment - Field2D V0, Dphi0; // net flow amplitude, differential potential to flux - Vector2D V0net; // net flow - - bool nogradparj; - bool filter_z; - int filter_z_mode; - int low_pass_z; - bool zonal_flow; - bool zonal_field; - bool zonal_bkgd; - bool relax_j_vac; - BoutReal relax_j_tconst; // Time-constant for j relax - Field3D Psitarget; // The (moving) target to relax to - - bool smooth_j_x; // Smooth Jpar in the x direction - BoutReal filter_nl; - - int jpar_bndry_width; // Zero jpar in a boundary region - - bool parallel_lagrange; // Use (semi-) Lagrangian method for parallel derivatives - bool parallel_project; // Use Apar to project field-lines - - //******************** - - Field3D Xip_x, Xip_z; // Displacement of y+1 (in cell index space) - - Field3D Xim_x, Xim_z; // Displacement of y-1 (in cell index space) - - bool phi_constraint; // Solver for phi using a solver constraint - - BoutReal vac_lund, core_lund; // Lundquist number S = (Tau_R / Tau_A). -ve -> infty - BoutReal vac_resist, core_resist; // The resistivities (just 1 / S) - Field3D eta; // Resistivity profile (1 / S) - bool spitzer_resist; // Use Spitzer formula for resistivity - - Field3D eta_spitzer; // Resistivity profile (kg*m^3 / S / C^2) - Field3D nu_i; // Ion collision frequency profile (1 / S) - Field3D nu_e; // Electron collision frequency profile (1 / S) - Field3D vth_i; // Ion Thermal Velocity profile (M / S) - Field3D vth_e; // Electron Thermal Velocity profile (M / S) - Field3D kappa_par_i; // Ion Thermal Conductivity profile (kg&M / S^2) - Field3D kappa_par_e; // Electron Thermal Conductivity profile (kg*M / S^2) - Field2D omega_ci, omega_ce; // cyclotron frequency - Field3D kappa_perp_i; // Ion perpendicular Thermal Conductivity profile (kg&M / S^2) - // Electron perpendicular Thermal Conductivity profile (kg*M / S^2) - Field3D kappa_perp_e; - - bool output_transfer; // output the results of energy transfer - bool output_ohm; // output the results of the terms in Ohm's law - bool output_flux_par; // output the results of parallel particle and heat flux - // Maxwell stress, Reynolds stress, ion diamagbetic and curvature term - Field3D T_M, T_R, T_ID, T_C, T_G; - Field3D ohm_phi, ohm_hall, ohm_thermal; - // particle flux, ion and elelctron heat flux - Field3D gamma_par_i, heatf_par_i, heatf_par_e; - - BoutReal hyperresist; // Hyper-resistivity coefficient (in core only) - BoutReal ehyperviscos; // electron Hyper-viscosity coefficient - Field3D hyper_eta_x; // Radial resistivity profile - Field3D hyper_eta_z; // Toroidal resistivity profile - - int damp_width; // Width of inner damped region - BoutReal damp_t_const; // Timescale of damping - - // Metric coefficients - Field2D Rxy, Bpxy, Btxy, B0, hthe; - Field2D I; // Shear factor - BoutReal LnLambda; // ln(Lambda) - - /// Ion mass - BoutReal Mi = SI::amu; - - /// Communication objects - FieldGroup comms; - - /// Solver for inverting Laplacian - std::unique_ptr phiSolver{nullptr}; - std::unique_ptr aparSolver{nullptr}; - - /// For printing out some diagnostics first time around - bool first_run = true; - - Field3D field_larger(const Field3D& f, const BoutReal limit) { - Field3D result; - result.allocate(); - - for (auto i : result) { - if (f[i] >= limit) { - result[i] = f[i]; - } else { - result[i] = limit; - } + /// The total height, average width and center of profile of N0 + BoutReal n0_height, n0_ave, n0_width, n0_center, n0_bottom_x; + /// The ampitude of congstant temperature + BoutReal Tconst; + + /// Test the effect of first order term of invert Laplace function + BoutReal laplace_alpha; + /// The ratio of Ti0/Te0 + BoutReal Tau_ie; + + // 2D inital profiles + /// Current and pressure + Field2D J0, P0; + /// Curvature term + Vector2D b0xcv; + /// When diamagnetic terms used + Field2D phi0; + + /// Number density and temperature for ions and electrons + Field2D N0, Ti0, Te0, Ne0; + Field2D Pi0, Pe0; + Field2D q95; + BoutReal q95_input; + bool local_q; + BoutReal q_alpha; + bool n0_fake_prof, T0_fake_prof; + /// Charge number of ion + BoutReal Zi; + + /// B0 field vector + Vector2D B0vec; + + // V0 field vectors + /// V0 field vector in convection + Vector2D V0vec; + /// Effective V0 field vector in Ohm's law + Vector2D V0eff; + + // 3D evolving variables + Field3D U, Psi, P, Pi, Pe; + Field3D Ni, Te, Ti, Ne; + Field3D Vipar, Vepar; + + // Derived 3D variables + Field3D Jpar, phi; // Parallel current, electric potential + + Field3D Ajpar; // Parallel current, electric potential + bool emass; + BoutReal emass_inv; // inverse of electron mass + BoutReal coef_jpar; + BoutReal delta_e; // Normalized electron skin depth + BoutReal delta_e_inv; // inverse normalized electron skin depth + BoutReal gyroAlv; // Normalized ion current coef + Field3D ubyn; + + Field3D Jpar2; // Delp2 of Parallel current + Field3D tmpA2; // Grad2_par2new of Parallel vector potential + Field3D tmpN2, tmpTi2, tmpTe2, tmpVp2; // Grad2_par2new of Parallel density + + // Constraint + Field3D C_phi; + + // Parameters + BoutReal density; // Number density [m^-3] + BoutReal Bbar, Lbar, Tbar, Va; // Normalisation constants + BoutReal Nbar, Tibar, Tebar; + BoutReal dia_fact; // Multiply diamagnetic term by this + + BoutReal diffusion_par; // Parallel thermal conductivity + BoutReal diffusion_perp; // Perpendicular thermal conductivity (>0 open) + BoutReal diffusion_n4, diffusion_ti4, + diffusion_te4; // M: 4th Parallel density diffusion + BoutReal diffusion_v4; + BoutReal diffusion_u4; // xqx: parallel hyper-viscous diffusion for vorticity + + BoutReal heating_P; // heating power in pressure + BoutReal hp_width; // heating profile radial width in pressure + BoutReal hp_length; // heating radial domain in pressure + BoutReal sink_vp; // sink in pressure + BoutReal sp_width; // sink profile radial width in pressure + BoutReal sp_length; // sink radial domain in pressure + + BoutReal sink_Ul; // left edge sink in vorticity + BoutReal su_widthl; // left edge sink profile radial width in vorticity + BoutReal su_lengthl; // left edge sink radial domain in vorticity + + BoutReal sink_Ur; // right edge sink in vorticity + BoutReal su_widthr; // right edge sink profile radial width in vorticity + BoutReal su_lengthr; // right edge sink radial domain in vorticity + + BoutReal viscos_par; // Parallel viscosity + BoutReal viscos_perp; // Perpendicular viscosity + BoutReal hyperviscos; // Hyper-viscosity (radial) + Field3D hyper_mu_x; // Hyper-viscosity coefficient + + Field3D Dperp2Phi0, Dperp2Phi, GradPhi02, + GradPhi2; // Temporary variables for gyroviscous + Field3D GradparPhi02, GradparPhi2, GradcPhi, GradcparPhi; + Field3D Dperp2Pi0, Dperp2Pi, bracketPhi0P, bracketPhiP0, bracketPhiP; + + BoutReal Psipara1, Upara0, + Upara1; // Temporary normalization constants for all the equations + BoutReal Upara2, Upara3, Nipara1; + BoutReal Tipara1, Tipara2; + BoutReal Tepara1, Tepara2, Tepara3, Tepara4; + BoutReal Vepara, Vipara; + BoutReal Low_limit; // To limit the negative value of total density and temperatures + + Field3D Te_tmp, Ti_tmp, N_tmp; // to avoid the negative value of total value + BoutReal gamma_i_BC, gamma_e_BC; // sheath energy transmission factors + int Sheath_width; + Field3D c_se, Jpar_sh, q_se, q_si, vth_et, + c_set; // variables for sheath boundary conditions + Field2D vth_e0, c_se0, Jpar_sh0; + BoutReal const_cse; + + // options + bool include_curvature, include_jpar0, compress0; + bool evolve_pressure, continuity, gyroviscous; + Field3D diff_radial, ddx_ni, ddx_n0; + BoutReal diffusion_coef_Hmode0, diffusion_coef_Hmode1; + Field3D eta_i0, pi_ci; + + BoutReal vacuum_pressure; + BoutReal vacuum_trans; // Transition width + Field3D vac_mask; + + bool nonlinear; + bool evolve_jpar; + BoutReal g; // Only if compressible + bool phi_curv; + + // Poisson brackets: b0 x Grad(f) dot Grad(g) / B = [f, g] + // Method to use: BRACKET_ARAKAWA, BRACKET_STD or BRACKET_SIMPLE + BRACKET_METHOD bm_exb, bm_mag; // Bracket method for advection terms + int bracket_method_exb, bracket_method_mag; + + bool diamag; + bool energy_flux, energy_exch; // energy flux term + bool diamag_phi0; // Include the diamagnetic equilibrium phi0 + bool thermal_force; // Include the thermal flux term in Ohm's law + bool eHall; + BoutReal AA; // ion mass in units of the proton mass; AA=Mi/Mp + + BoutReal Vt0; // equilibrium toroidal flow normalized to Alfven velocity + BoutReal Vp0; // equilibrium poloidal flow normalized to Alfven velocity + + bool experiment_Er; // read in phi_0 from experiment + Field2D V0, Dphi0; // net flow amplitude, differential potential to flux + Vector2D V0net; // net flow + + bool nogradparj; + bool filter_z; + int filter_z_mode; + int low_pass_z; + bool zonal_flow; + bool zonal_field; + bool zonal_bkgd; + bool relax_j_vac; + BoutReal relax_j_tconst; // Time-constant for j relax + Field3D Psitarget; // The (moving) target to relax to + + bool smooth_j_x; // Smooth Jpar in the x direction + BoutReal filter_nl; + + int jpar_bndry_width; // Zero jpar in a boundary region + + bool parallel_lagrange; // Use (semi-) Lagrangian method for parallel derivatives + bool parallel_project; // Use Apar to project field-lines + + //******************** + + Field3D Xip_x, Xip_z; // Displacement of y+1 (in cell index space) + + Field3D Xim_x, Xim_z; // Displacement of y-1 (in cell index space) + + bool phi_constraint; // Solver for phi using a solver constraint + + BoutReal vac_lund, core_lund; // Lundquist number S = (Tau_R / Tau_A). -ve -> infty + BoutReal vac_resist, core_resist; // The resistivities (just 1 / S) + Field3D eta; // Resistivity profile (1 / S) + bool spitzer_resist; // Use Spitzer formula for resistivity + + Field3D eta_spitzer; // Resistivity profile (kg*m^3 / S / C^2) + Field3D nu_i; // Ion collision frequency profile (1 / S) + Field3D nu_e; // Electron collision frequency profile (1 / S) + Field3D vth_i; // Ion Thermal Velocity profile (M / S) + Field3D vth_e; // Electron Thermal Velocity profile (M / S) + Field3D kappa_par_i; // Ion Thermal Conductivity profile (kg&M / S^2) + Field3D kappa_par_e; // Electron Thermal Conductivity profile (kg*M / S^2) + Field2D omega_ci, omega_ce; // cyclotron frequency + Field3D kappa_perp_i; // Ion perpendicular Thermal Conductivity profile (kg&M / S^2) + // Electron perpendicular Thermal Conductivity profile (kg*M / S^2) + Field3D kappa_perp_e; + + bool output_transfer; // output the results of energy transfer + bool output_ohm; // output the results of the terms in Ohm's law + bool output_flux_par; // output the results of parallel particle and heat flux + // Maxwell stress, Reynolds stress, ion diamagbetic and curvature term + Field3D T_M, T_R, T_ID, T_C, T_G; + Field3D ohm_phi, ohm_hall, ohm_thermal; + // particle flux, ion and elelctron heat flux + Field3D gamma_par_i, heatf_par_i, heatf_par_e; + + BoutReal hyperresist; // Hyper-resistivity coefficient (in core only) + BoutReal ehyperviscos; // electron Hyper-viscosity coefficient + Field3D hyper_eta_x; // Radial resistivity profile + Field3D hyper_eta_z; // Toroidal resistivity profile + + int damp_width; // Width of inner damped region + BoutReal damp_t_const; // Timescale of damping + + bout::TokamakOptions tokamak_options = bout::TokamakOptions(*mesh); + + BoutReal LnLambda; // ln(Lambda) + + /// Ion mass + BoutReal Mi = SI::amu; + + /// Communication objects + FieldGroup comms; + + /// Solver for inverting Laplacian + std::unique_ptr phiSolver{nullptr}; + std::unique_ptr aparSolver{nullptr}; + + /// For printing out some diagnostics first time around + bool first_run = true; + + Field3D field_larger(const Field3D &f, const BoutReal limit) { + Field3D result; + result.allocate(); + + for (auto i: result) { + if (f[i] >= limit) { + result[i] = f[i]; + } else { + result[i] = limit; + } + } + mesh->communicate(result); + return result; } - mesh->communicate(result); - return result; - } - Field3D Grad2_par2new(const Field3D& f) { - /* - * This function implements d2/dy2 where y is the poloidal coordinate theta - */ + Field3D Grad2_par2new(const Field3D &f) { + /* + * This function implements d2/dy2 where y is the poloidal coordinate theta + */ - TRACE("Grad2_par2new( Field3D )"); + TRACE("Grad2_par2new( Field3D )"); - Field3D result = D2DY2(f); + Field3D result = D2DY2(f); #if BOUT_USE_TRACK - result.name = "Grad2_par2new(" + f.name + ")"; + result.name = "Grad2_par2new(" + f.name + ")"; #endif - return result; - } - - Field2D N0tanh(BoutReal n0_height, BoutReal n0_ave, BoutReal n0_width, - BoutReal n0_center, BoutReal n0_bottom_x) { - Field2D result; - result.allocate(); - - BoutReal Grid_NX, Grid_NXlimit; // the grid number on x, and the - BoutReal Jysep; - mesh->get(Grid_NX, "nx"); - mesh->get(Jysep, "jyseps1_1"); - Grid_NXlimit = n0_bottom_x * Grid_NX; - output.write("Jysep1_1 = {:d} Grid number = {:e}\n", int(Jysep), Grid_NX); - - if (Jysep > 0.) { // for single null geometry - - BoutReal Jxsep, Jysep2; - mesh->get(Jxsep, "ixseps1"); - mesh->get(Jysep2, "jyseps2_2"); - - for (int jx = 0; jx < mesh->LocalNx; jx++) { - BoutReal mgx = mesh->GlobalX(jx); - BoutReal xgrid_num = (Jxsep + 1.) / Grid_NX; - // output.write("mgx = {:e} xgrid_num = {:e}\n", mgx); - for (int jy = 0; jy < mesh->LocalNy; jy++) { - int globaly = mesh->getGlobalYIndex(jy); - // output.write("local y = {:d}; global y: {:d}\n", jy, globaly); - if (mgx > xgrid_num || (globaly <= int(Jysep) - 2) - || (globaly > int(Jysep2) + 2)) { - mgx = xgrid_num; - } - BoutReal rlx = mgx - n0_center; - BoutReal temp = exp(rlx / n0_width); - BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); - result(jx, jy) = 0.5 * (1.0 - dampr) * n0_height + n0_ave; - } - } - } else { // circular geometry - for (int jx = 0; jx < mesh->LocalNx; jx++) { - BoutReal mgx = mesh->GlobalX(jx); - BoutReal xgrid_num = Grid_NXlimit / Grid_NX; - if (mgx > xgrid_num) { - mgx = xgrid_num; - } - BoutReal rlx = mgx - n0_center; - BoutReal temp = exp(rlx / n0_width); - BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); - for (int jy = 0; jy < mesh->LocalNy; jy++) { - result(jx, jy) = 0.5 * (1.0 - dampr) * n0_height + n0_ave; - } - } - } + return result; + } + + Field2D N0tanh(BoutReal n0_height, BoutReal n0_ave, BoutReal n0_width, + BoutReal n0_center, BoutReal n0_bottom_x) { + Field2D result; + result.allocate(); + + BoutReal Grid_NX, Grid_NXlimit; // the grid number on x, and the + BoutReal Jysep; + mesh->get(Grid_NX, "nx"); + mesh->get(Jysep, "jyseps1_1"); + Grid_NXlimit = n0_bottom_x * Grid_NX; + output.write("Jysep1_1 = {:d} Grid number = {:e}\n", int(Jysep), Grid_NX); + + if (Jysep > 0.) { // for single null geometry + + BoutReal Jxsep, Jysep2; + mesh->get(Jxsep, "ixseps1"); + mesh->get(Jysep2, "jyseps2_2"); + + for (int jx = 0; jx < mesh->LocalNx; jx++) { + BoutReal mgx = mesh->GlobalX(jx); + BoutReal xgrid_num = (Jxsep + 1.) / Grid_NX; + // output.write("mgx = {:e} xgrid_num = {:e}\n", mgx); + for (int jy = 0; jy < mesh->LocalNy; jy++) { + int globaly = mesh->getGlobalYIndex(jy); + // output.write("local y = {:d}; global y: {:d}\n", jy, globaly); + if (mgx > xgrid_num || (globaly <= int(Jysep) - 2) + || (globaly > int(Jysep2) + 2)) { + mgx = xgrid_num; + } + BoutReal rlx = mgx - n0_center; + BoutReal temp = exp(rlx / n0_width); + BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); + result(jx, jy) = 0.5 * (1.0 - dampr) * n0_height + n0_ave; + } + } + } else { // circular geometry + for (int jx = 0; jx < mesh->LocalNx; jx++) { + BoutReal mgx = mesh->GlobalX(jx); + BoutReal xgrid_num = Grid_NXlimit / Grid_NX; + if (mgx > xgrid_num) { + mgx = xgrid_num; + } + BoutReal rlx = mgx - n0_center; + BoutReal temp = exp(rlx / n0_width); + BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); + for (int jy = 0; jy < mesh->LocalNy; jy++) { + result(jx, jy) = 0.5 * (1.0 - dampr) * n0_height + n0_ave; + } + } + } - mesh->communicate(result); + mesh->communicate(result); - return result; - } + return result; + } - // Parallel gradient along perturbed field-line - Field3D Grad_parP(const Field3D& f, CELL_LOC loc = CELL_DEFAULT) { - TRACE("Grad_parP"); + // Parallel gradient along perturbed field-line + Field3D Grad_parP(const Field3D &f, CELL_LOC loc = CELL_DEFAULT) { + TRACE("Grad_parP"); - Field3D result; + Field3D result; - if (parallel_lagrange || parallel_project) { - // Moving stencil locations + if (parallel_lagrange || parallel_project) { + // Moving stencil locations - ASSERT2((not mesh->StaggerGrids) or loc == CELL_DEFAULT or loc == f.getLocation()); + ASSERT2((not mesh->StaggerGrids) or loc == CELL_DEFAULT or loc == f.getLocation()); - Field3D fp, fm; // Interpolated on + and - y locations + Field3D fp, fm; // Interpolated on + and - y locations - fp = interpolate(f, Xip_x, Xip_z); - fm = interpolate(f, Xim_x, Xim_z); + fp = interpolate(f, Xip_x, Xip_z); + fm = interpolate(f, Xim_x, Xim_z); - Coordinates* coord = mesh->getCoordinates(); + Coordinates *coord = mesh->getCoordinates(); - result.allocate(); - for (auto i : result) { - result[i] = - (fp[i.yp()] - fm[i.ym()]) / (2. * coord->dy()[i] * sqrt(coord->g_22()[i])); - } - } else { - result = Grad_par(f, loc); + result.allocate(); + for (auto i: result) { + result[i] = + (fp[i.yp()] - fm[i.ym()]) / (2. * coord->dy()[i] * sqrt(coord->g_22()[i])); + } + } else { + result = Grad_par(f, loc); - if (nonlinear) { - result -= bracket(Psi, f, bm_mag) * B0; - } - } + if (nonlinear) { + result -= bracket(Psi, f, bm_mag) * tokamak_options.Bxy; + } + } - return result; - } + return result; + } protected: - int init(bool restarting) override { - bool noshear; + int init(bool restarting) override { + + bool noshear; + + // Get the metric tensor + Coordinates *coord = mesh->getCoordinates(); + + output.write("Solving high-beta flute reduced equations\n"); + output.write("\tFile : {:s}\n", __FILE__); + output.write("\tCompiled: {:s} at {:s}\n", __DATE__, __TIME__); + + ////////////////////////////////////////////////////////////// + // Load data from the grid + + // Load 2D profiles + mesh->get(J0, "Jpar0"); // A / m^2 + mesh->get(P0, "pressure"); // Pascals + + // Load curvature term + b0xcv.covariant = false; // Read contravariant components + mesh->get(b0xcv, "bxcv"); // mixed units x: T y: m^-2 z: m^-2 + + ////////////////////////////////////////////////////////////// + // Read parameters from the options file + // + // Options.get ( NAME, VARIABLE, DEFAULT VALUE) + // + // or if NAME = "VARIABLE" then just + // + // OPTION(VARIABLE, DEFAULT VALUE) + // + // Prints out what values are assigned + ///////////////////////////////////////////////////////////// + + auto &globalOptions = Options::root(); + auto &options = globalOptions["highbeta"]; + + n0_fake_prof = options["n0_fake_prof"] + .doc("use the hyperbolic profile of n0. If both n0_fake_prof and " + "T0_fake_prof are " + "false, use the profiles from grid file") + .withDefault(false); + n0_height = options["n0_height"] + .doc("the total height of profile of N0, in percentage of Ni_x") + .withDefault(0.4); + n0_ave = options["n0_ave"] + .doc("the center or average of N0, in percentage of Ni_x") + .withDefault(0.01); + n0_width = options["n0_width"] + .doc("the width of the gradient of N0,in percentage of x") + .withDefault(0.1); + n0_center = options["n0_center"] + .doc("the grid number of the center of N0, in percentage of x") + .withDefault(0.633); + n0_bottom_x = + options["n0_bottom_x"] + .doc("the start of flat region of N0 on SOL side, in percentage of x") + .withDefault(0.81); + T0_fake_prof = options["T0_fake_prof"].doc("").withDefault(false); + Tconst = options["Tconst"] + .doc("the amplitude of constant temperature, in percentage") + .withDefault(-1.0); + + experiment_Er = options["experiment_Er"].withDefault(false); + + laplace_alpha = options["laplace_alpha"] + .doc("test parameter for the cross term of invert Lapalace") + .withDefault(1.0); + Low_limit = options["Low_limit"] + .doc("limit the negative value of total quantities") + .withDefault(1.0e-10); + q95_input = options["q95_input"] + .doc("input q95 as a constant, if <0 use profile from grid") + .withDefault(5.0); + local_q = options["local_q"] + .doc("Using magnetic field to calculate q profile?") + .withDefault(false); + q_alpha = options["q_alpha"] + .doc("flux-limiting coefficient, typical value is [0.03, 3]") + .withDefault(1.0); + + gamma_i_BC = options["gamma_i_BC"] + .doc("sheath energy transmission factor for ion") + .withDefault(-1.0); + gamma_e_BC = options["gamma_e_BC"] + .doc("sheath energy transmission factor for electron") + .withDefault(-1.0); + Sheath_width = options["Sheath_width"] + .doc("Sheath boundary width in grid number") + .withDefault(1); + + density = options["density"].doc("Number density [m^-3]").withDefault(1.0e19); + Zi = options["Zi"].doc("ion charge number").withDefault(1); + continuity = options["continuity"].doc("use continuity equation").withDefault(false); + + evolve_jpar = + options["evolve_jpar"].doc("If true, evolve J raher than Psi").withDefault(false); + phi_constraint = + options["phi_constraint"].doc("Use solver constraint for phi").withDefault(false); + + // Effects to include/exclude + include_curvature = options["include_curvature"].withDefault(true); + include_jpar0 = options["include_jpar0"].withDefault(true); + evolve_pressure = options["evolve_pressure"].withDefault(true); + + compress0 = options["compress0"].withDefault(false); + nonlinear = options["nonlinear"].withDefault(false); + + // int bracket_method; + bracket_method_exb = options["bracket_method_exb"].withDefault(0); + switch (bracket_method_exb) { + case 0: { + bm_exb = BRACKET_STD; + output << "\tBrackets for ExB: default differencing\n"; + break; + } + case 1: { + bm_exb = BRACKET_SIMPLE; + output << "\tBrackets for ExB: simplified operator\n"; + break; + } + case 2: { + bm_exb = BRACKET_ARAKAWA; + output << "\tBrackets for ExB: Arakawa scheme\n"; + break; + } + case 3: { + bm_exb = BRACKET_CTU; + output << "\tBrackets for ExB: Corner Transport Upwind method\n"; + break; + } + default: + output << "ERROR: Invalid choice of bracket method. Must be 0 - 3\n"; + return 1; + } - // Get the metric tensor - Coordinates* coord = mesh->getCoordinates(); + // int bracket_method; + bracket_method_mag = options["bracket_method_mag"].withDefault(2); + switch (bracket_method_mag) { + case 0: { + bm_mag = BRACKET_STD; + output << "\tBrackets: default differencing\n"; + break; + } + case 1: { + bm_mag = BRACKET_SIMPLE; + output << "\tBrackets: simplified operator\n"; + break; + } + case 2: { + bm_mag = BRACKET_ARAKAWA; + output << "\tBrackets: Arakawa scheme\n"; + break; + } + case 3: { + bm_mag = BRACKET_CTU; + output << "\tBrackets: Corner Transport Upwind method\n"; + break; + } + default: + output << "ERROR: Invalid choice of bracket method. Must be 0 - 3\n"; + return 1; + } - output.write("Solving high-beta flute reduced equations\n"); - output.write("\tFile : {:s}\n", __FILE__); - output.write("\tCompiled: {:s} at {:s}\n", __DATE__, __TIME__); + AA = options["AA"].doc("ion mass in units of proton mass").withDefault(1.0); + Mi *= AA; - ////////////////////////////////////////////////////////////// - // Load data from the grid + emass = options["emass"] + .doc("including electron inertial, electron mass") + .withDefault(false); + emass_inv = options["emass_inv"].doc("inverse of electron mass").withDefault(1.0); - // Load 2D profiles - mesh->get(J0, "Jpar0"); // A / m^2 - mesh->get(P0, "pressure"); // Pascals + diamag = options["diamag"].doc("Diamagnetic effects?").withDefault(false); + diamag_phi0 = + options["diamag_phi0"].doc("Include equilibrium phi0").withDefault(diamag); + dia_fact = options["dia_fact"] + .doc("Scale diamagnetic effects by this factor") + .withDefault(1.0); - // Load curvature term - b0xcv.covariant = false; // Read contravariant components - mesh->get(b0xcv, "bxcv"); // mixed units x: T y: m^-2 z: m^-2 + noshear = options["noshear"].withDefault(false); - // Load metrics - if (mesh->get(Rxy, "Rxy")) { // m - output_error.write("Error: Cannot read Rxy from grid\n"); - return 1; - } - if (mesh->get(Bpxy, "Bpxy")) { // T - output_error.write("Error: Cannot read Bpxy from grid\n"); - return 1; - } - mesh->get(Btxy, "Btxy"); // T - mesh->get(B0, "Bxy"); // T - mesh->get(hthe, "hthe"); // m - mesh->get(I, "sinty"); // m^-2 T^-1 - - ////////////////////////////////////////////////////////////// - // Read parameters from the options file - // - // Options.get ( NAME, VARIABLE, DEFAULT VALUE) - // - // or if NAME = "VARIABLE" then just - // - // OPTION(VARIABLE, DEFAULT VALUE) - // - // Prints out what values are assigned - ///////////////////////////////////////////////////////////// - - auto& globalOptions = Options::root(); - auto& options = globalOptions["highbeta"]; - - n0_fake_prof = options["n0_fake_prof"] - .doc("use the hyperbolic profile of n0. If both n0_fake_prof and " - "T0_fake_prof are " - "false, use the profiles from grid file") - .withDefault(false); - n0_height = options["n0_height"] - .doc("the total height of profile of N0, in percentage of Ni_x") - .withDefault(0.4); - n0_ave = options["n0_ave"] - .doc("the center or average of N0, in percentage of Ni_x") - .withDefault(0.01); - n0_width = options["n0_width"] - .doc("the width of the gradient of N0,in percentage of x") - .withDefault(0.1); - n0_center = options["n0_center"] - .doc("the grid number of the center of N0, in percentage of x") - .withDefault(0.633); - n0_bottom_x = - options["n0_bottom_x"] - .doc("the start of flat region of N0 on SOL side, in percentage of x") - .withDefault(0.81); - T0_fake_prof = options["T0_fake_prof"].doc("").withDefault(false); - Tconst = options["Tconst"] - .doc("the amplitude of constant temperature, in percentage") - .withDefault(-1.0); - - experiment_Er = options["experiment_Er"].withDefault(false); - - laplace_alpha = options["laplace_alpha"] - .doc("test parameter for the cross term of invert Lapalace") - .withDefault(1.0); - Low_limit = options["Low_limit"] - .doc("limit the negative value of total quantities") - .withDefault(1.0e-10); - q95_input = options["q95_input"] - .doc("input q95 as a constant, if <0 use profile from grid") - .withDefault(5.0); - local_q = options["local_q"] - .doc("Using magnetic field to calculate q profile?") - .withDefault(false); - q_alpha = options["q_alpha"] - .doc("flux-limiting coefficient, typical value is [0.03, 3]") - .withDefault(1.0); - - gamma_i_BC = options["gamma_i_BC"] - .doc("sheath energy transmission factor for ion") - .withDefault(-1.0); - gamma_e_BC = options["gamma_e_BC"] - .doc("sheath energy transmission factor for electron") - .withDefault(-1.0); - Sheath_width = options["Sheath_width"] - .doc("Sheath boundary width in grid number") - .withDefault(1); - - density = options["density"].doc("Number density [m^-3]").withDefault(1.0e19); - Zi = options["Zi"].doc("ion charge number").withDefault(1); - continuity = options["continuity"].doc("use continuity equation").withDefault(false); - - evolve_jpar = - options["evolve_jpar"].doc("If true, evolve J raher than Psi").withDefault(false); - phi_constraint = - options["phi_constraint"].doc("Use solver constraint for phi").withDefault(false); - - // Effects to include/exclude - include_curvature = options["include_curvature"].withDefault(true); - include_jpar0 = options["include_jpar0"].withDefault(true); - evolve_pressure = options["evolve_pressure"].withDefault(true); - - compress0 = options["compress0"].withDefault(false); - nonlinear = options["nonlinear"].withDefault(false); - - // int bracket_method; - bracket_method_exb = options["bracket_method_exb"].withDefault(0); - switch (bracket_method_exb) { - case 0: { - bm_exb = BRACKET_STD; - output << "\tBrackets for ExB: default differencing\n"; - break; - } - case 1: { - bm_exb = BRACKET_SIMPLE; - output << "\tBrackets for ExB: simplified operator\n"; - break; - } - case 2: { - bm_exb = BRACKET_ARAKAWA; - output << "\tBrackets for ExB: Arakawa scheme\n"; - break; - } - case 3: { - bm_exb = BRACKET_CTU; - output << "\tBrackets for ExB: Corner Transport Upwind method\n"; - break; - } - default: - output << "ERROR: Invalid choice of bracket method. Must be 0 - 3\n"; - return 1; - } + relax_j_vac = + options["relax_j_vac"].doc("Relax vacuum current to zero").withDefault(false); + relax_j_tconst = options["relax_j_tconst"].withDefault(0.1); - // int bracket_method; - bracket_method_mag = options["bracket_method_mag"].withDefault(2); - switch (bracket_method_mag) { - case 0: { - bm_mag = BRACKET_STD; - output << "\tBrackets: default differencing\n"; - break; - } - case 1: { - bm_mag = BRACKET_SIMPLE; - output << "\tBrackets: simplified operator\n"; - break; - } - case 2: { - bm_mag = BRACKET_ARAKAWA; - output << "\tBrackets: Arakawa scheme\n"; - break; - } - case 3: { - bm_mag = BRACKET_CTU; - output << "\tBrackets: Corner Transport Upwind method\n"; - break; - } - default: - output << "ERROR: Invalid choice of bracket method. Must be 0 - 3\n"; - return 1; - } + // Toroidal filtering + filter_z = options["filter_z"].doc("Filter a single n").withDefault(false); + filter_z_mode = options["filter_z_mode"].withDefault(1); + low_pass_z = options["low_pass_z"].doc("Low pass filter. < 0 -> off").withDefault(-1); + zonal_flow = options["zonal_flow"].withDefault(false); // zonal flow filter + zonal_field = options["zonal_field"].withDefault(false); // zonal field filter + zonal_bkgd = options["zonal_bkgd"].withDefault(false); // zonal background P filter - AA = options["AA"].doc("ion mass in units of proton mass").withDefault(1.0); - Mi *= AA; + filter_nl = options["filter_nl"].doc("zonal background P filter").withDefault(-1); - emass = options["emass"] - .doc("including electron inertial, electron mass") - .withDefault(false); - emass_inv = options["emass_inv"].doc("inverse of electron mass").withDefault(1.0); - - diamag = options["diamag"].doc("Diamagnetic effects?").withDefault(false); - diamag_phi0 = - options["diamag_phi0"].doc("Include equilibrium phi0").withDefault(diamag); - dia_fact = options["dia_fact"] - .doc("Scale diamagnetic effects by this factor") - .withDefault(1.0); - - noshear = options["noshear"].withDefault(false); - - relax_j_vac = - options["relax_j_vac"].doc("Relax vacuum current to zero").withDefault(false); - relax_j_tconst = options["relax_j_tconst"].withDefault(0.1); - - // Toroidal filtering - filter_z = options["filter_z"].doc("Filter a single n").withDefault(false); - filter_z_mode = options["filter_z_mode"].withDefault(1); - low_pass_z = options["low_pass_z"].doc("Low pass filter. < 0 -> off").withDefault(-1); - zonal_flow = options["zonal_flow"].withDefault(false); // zonal flow filter - zonal_field = options["zonal_field"].withDefault(false); // zonal field filter - zonal_bkgd = options["zonal_bkgd"].withDefault(false); // zonal background P filter - - filter_nl = options["filter_nl"].doc("zonal background P filter").withDefault(-1); - - // Radial smoothing - smooth_j_x = options["smooth_j_x"].doc("Smooth Jpar in x").withDefault(false); - - // Jpar boundary region - jpar_bndry_width = options["jpar_bndry_width"].withDefault(-1); - - // Parallel differencing - parallel_lagrange = options["parallel_lagrange"] - .doc("Use a (semi-) Lagrangian method for Grad_parP") - .withDefault(false); - parallel_project = options["parallel_project"].withDefault(false); - - // Vacuum region control - vacuum_pressure = - options["vacuum_pressure"].doc("Fraction of peak pressure").withDefault(0.02); - vacuum_trans = - options["vacuum_trans"].doc("Transition width in pressure").withDefault(0.005); - - // Resistivity and hyper-resistivity options - vac_lund = - options["vac_lund"].doc("Lundquist number in vacuum region").withDefault(0.0); - core_lund = - options["core_lund"].doc("Lundquist number in core region").withDefault(0.0); - hyperresist = options["hyperresist"].withDefault(-1.0); - ehyperviscos = options["ehyperviscos"].withDefault(-1.0); - spitzer_resist = - options["spitzer_resist"].doc("Use Spitzer resistivity?").withDefault(false); - - // Inner boundary damping - damp_width = options["damp_width"].withDefault(0); - damp_t_const = options["damp_t_const"].withDefault(0.1); - - // Viscosity and hyper-viscosity - viscos_par = options["viscos_par"].doc("Parallel viscosity").withDefault(-1.0); - viscos_perp = options["viscos_perp"].doc("Perpendicular viscosity").withDefault(-1.0); - hyperviscos = options["hyperviscos"].doc("Radial hyperviscosity").withDefault(-1.0); - - diffusion_par = - options["diffusion_par"].doc("Parallel temperature diffusion").withDefault(-1.0); - diffusion_n4 = - options["diffusion_n4"].doc("4th Parallel density diffusion").withDefault(-1.0); - diffusion_ti4 = options["diffusion_ti4"] - .doc("4th Parallel ion temperature diffusion") - .withDefault(-1.0); - diffusion_te4 = options["diffusion_te4"] - .doc("4th Parallel electron temperature diffusion") - .withDefault(-1.0); - diffusion_u4 = options["diffusion_u4"] - .doc("parallel hyper-viscous diffusion for vorticity") - .withDefault(-1.0); - diffusion_v4 = options["diffusion_v4"] - .doc("4th order Parallel ion velocity diffusion (< 0 = none)") - .withDefault(-1.0); - - // heating factor in pressure - heating_P = options["heating_P"].doc("heating power in pressure").withDefault(-1.0); - // - hp_width = options["hp_width"] - .doc("the percentage of radial grid points for heating profile radial " - "width in pressure") - .withDefault(0.1); - hp_length = options["hp_length"] - .doc("the percentage of radial grid points for heating profile " - "radial domain in pressure") - .withDefault(0.04); - - // sink factor in pressure - sink_vp = options["sink_vp"].doc("sink in pressure").withDefault(-1.0); - sp_width = options["sp_width"] - .doc("the percentage of radial grid points for sink profile radial " - "width in pressure") - .withDefault(0.05); - sp_length = options["sp_length"] - .doc("the percentage of radial grid points for sink profile radial " - "domain in pressure") - .withDefault(0.04); - - // left edge sink factor in vorticity - sink_Ul = options["sink_Ul"].doc("left edge sink in vorticity").withDefault(-1.0); - su_widthl = options["su_widthl"] - .doc("the percentage of left edge radial grid points for sink " - "profile radial width in vorticity") - .withDefault(0.06); - su_lengthl = options["su_lengthl"] - .doc("the percentage of left edge radial grid points for sink " - "profile radial domain in vorticity") - .withDefault(0.15); - - // right edge sink factor in vorticity - // right edge sink in vorticity - sink_Ur = options["sink_Ur"].doc("").withDefault(-1.0); - // the percentage of right edge radial grid points for sink profile - // radial width in vorticity - su_widthr = options["su_widthr"].doc("").withDefault(0.06); - // the percentage of right edge radial grid points for sink profile - // radial domain in vorticity - su_lengthr = options["su_lengthr"].doc("").withDefault(0.15); - - // Compressional terms - phi_curv = options["phi_curv"].doc("Compressional ExB terms").withDefault(true); - g = options["gamma"].doc("Ratio of specific heats").withDefault(5.0 / 3.0); - - if (!include_curvature) { - b0xcv = 0.0; - } + // Radial smoothing + smooth_j_x = options["smooth_j_x"].doc("Smooth Jpar in x").withDefault(false); - if (!include_jpar0) { - J0 = 0.0; - } + // Jpar boundary region + jpar_bndry_width = options["jpar_bndry_width"].withDefault(-1); - if (noshear) { - if (include_curvature) { - b0xcv.z += I * b0xcv.x; - } - I = 0.0; - } + // Parallel differencing + parallel_lagrange = options["parallel_lagrange"] + .doc("Use a (semi-) Lagrangian method for Grad_parP") + .withDefault(false); + parallel_project = options["parallel_project"].withDefault(false); + + // Vacuum region control + vacuum_pressure = + options["vacuum_pressure"].doc("Fraction of peak pressure").withDefault(0.02); + vacuum_trans = + options["vacuum_trans"].doc("Transition width in pressure").withDefault(0.005); + + // Resistivity and hyper-resistivity options + vac_lund = + options["vac_lund"].doc("Lundquist number in vacuum region").withDefault(0.0); + core_lund = + options["core_lund"].doc("Lundquist number in core region").withDefault(0.0); + hyperresist = options["hyperresist"].withDefault(-1.0); + ehyperviscos = options["ehyperviscos"].withDefault(-1.0); + spitzer_resist = + options["spitzer_resist"].doc("Use Spitzer resistivity?").withDefault(false); + + // Inner boundary damping + damp_width = options["damp_width"].withDefault(0); + damp_t_const = options["damp_t_const"].withDefault(0.1); + + // Viscosity and hyper-viscosity + viscos_par = options["viscos_par"].doc("Parallel viscosity").withDefault(-1.0); + viscos_perp = options["viscos_perp"].doc("Perpendicular viscosity").withDefault(-1.0); + hyperviscos = options["hyperviscos"].doc("Radial hyperviscosity").withDefault(-1.0); + + diffusion_par = + options["diffusion_par"].doc("Parallel temperature diffusion").withDefault(-1.0); + diffusion_n4 = + options["diffusion_n4"].doc("4th Parallel density diffusion").withDefault(-1.0); + diffusion_ti4 = options["diffusion_ti4"] + .doc("4th Parallel ion temperature diffusion") + .withDefault(-1.0); + diffusion_te4 = options["diffusion_te4"] + .doc("4th Parallel electron temperature diffusion") + .withDefault(-1.0); + diffusion_u4 = options["diffusion_u4"] + .doc("parallel hyper-viscous diffusion for vorticity") + .withDefault(-1.0); + diffusion_v4 = options["diffusion_v4"] + .doc("4th order Parallel ion velocity diffusion (< 0 = none)") + .withDefault(-1.0); + + // heating factor in pressure + heating_P = options["heating_P"].doc("heating power in pressure").withDefault(-1.0); + // + hp_width = options["hp_width"] + .doc("the percentage of radial grid points for heating profile radial " + "width in pressure") + .withDefault(0.1); + hp_length = options["hp_length"] + .doc("the percentage of radial grid points for heating profile " + "radial domain in pressure") + .withDefault(0.04); + + // sink factor in pressure + sink_vp = options["sink_vp"].doc("sink in pressure").withDefault(-1.0); + sp_width = options["sp_width"] + .doc("the percentage of radial grid points for sink profile radial " + "width in pressure") + .withDefault(0.05); + sp_length = options["sp_length"] + .doc("the percentage of radial grid points for sink profile radial " + "domain in pressure") + .withDefault(0.04); + + // left edge sink factor in vorticity + sink_Ul = options["sink_Ul"].doc("left edge sink in vorticity").withDefault(-1.0); + su_widthl = options["su_widthl"] + .doc("the percentage of left edge radial grid points for sink " + "profile radial width in vorticity") + .withDefault(0.06); + su_lengthl = options["su_lengthl"] + .doc("the percentage of left edge radial grid points for sink " + "profile radial domain in vorticity") + .withDefault(0.15); + + // right edge sink factor in vorticity + // right edge sink in vorticity + sink_Ur = options["sink_Ur"].doc("").withDefault(-1.0); + // the percentage of right edge radial grid points for sink profile + // radial width in vorticity + su_widthr = options["su_widthr"].doc("").withDefault(0.06); + // the percentage of right edge radial grid points for sink profile + // radial domain in vorticity + su_lengthr = options["su_lengthr"].doc("").withDefault(0.15); + + // Compressional terms + phi_curv = options["phi_curv"].doc("Compressional ExB terms").withDefault(true); + g = options["gamma"].doc("Ratio of specific heats").withDefault(5.0 / 3.0); + + if (!include_curvature) { + b0xcv = 0.0; + } - ////////////////////////////////////////////////////////////// - // SHIFTED RADIAL COORDINATES + if (!include_jpar0) { + J0 = 0.0; + } - if (mesh->IncIntShear) { - // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - coord->setIntShiftTorsion(I); + BoutReal shearFactor = 1.0; + if (noshear) { + if (include_curvature) { + b0xcv.z += tokamak_options.I * b0xcv.x; + } + shearFactor = 0.0; + } - } else { - // Dimits style, using local coordinate system - if (include_curvature) { - b0xcv.z += I * b0xcv.x; - } - I = 0.0; // I disappears from metric - } + ////////////////////////////////////////////////////////////// + // SHIFTED RADIAL COORDINATES - ////////////////////////////////////////////////////////////// - // NORMALISE QUANTITIES + if (mesh->IncIntShear) { + // BOUT-06 style, using d/dx = d/dpsi + I * d/dz + coord->setIntShiftTorsion(tokamak_options.I); - if (mesh->get(Bbar, "bmag")) { // Typical magnetic field - Bbar = 1.0; - } - if (mesh->get(Lbar, "rmag")) { // Typical length scale - Lbar = 1.0; - } + } else { + // Dimits style, using local coordinate system + if (include_curvature) { + b0xcv.z += tokamak_options.I * b0xcv.x; + } + shearFactor = 0.0; // I disappears from metric + } - if (mesh->get(Tibar, "Ti_x")) { // Typical ion temperature scale - Tibar = 1.0; - } + ////////////////////////////////////////////////////////////// + // NORMALISE QUANTITIES - if (mesh->get(Tebar, "Te_x")) { // Typical electron temperature scale - Tebar = 1.0; - } + if (mesh->get(Bbar, "bmag")) { // Typical magnetic field + Bbar = 1.0; + } + if (mesh->get(Lbar, "rmag")) { // Typical length scale + Lbar = 1.0; + } - if (mesh->get(Nbar, "Nixexp")) { // Typical ion density scale - Nbar = 1.0; - } - Nbar *= 1.e20 / density; + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lbar, Bbar, shearFactor); - Tau_ie = Tibar / Tebar; + if (mesh->get(Tibar, "Ti_x")) { // Typical ion temperature scale + Tibar = 1.0; + } - Va = sqrt(Bbar * Bbar / (SI::mu0 * Mi * Nbar * density)); + if (mesh->get(Tebar, "Te_x")) { // Typical electron temperature scale + Tebar = 1.0; + } - Tbar = Lbar / Va; + if (mesh->get(Nbar, "Nixexp")) { // Typical ion density scale + Nbar = 1.0; + } + Nbar *= 1.e20 / density; - output.write("Normalisations: Bbar = {:e} T Lbar = {:e} m\n", Bbar, Lbar); - output.write(" Va = {:e} m/s Tbar = {:e} s\n", Va, Tbar); - output.write(" Nbar = {:e} * {:e} m^-3\n", Nbar, density); - output.write("Tibar = {:e} eV Tebar = {:e} eV Ti/Te = {:e}\n", Tibar, Tebar, - Tau_ie); - output.write(" Resistivity\n"); + Tau_ie = Tibar / Tebar; - Upara0 = SI::kb * Tebar * eV_K / (Zi * SI::qe * Bbar * Va * Lbar); - Upara1 = SI::kb * Tebar * eV_K / Mi / Va / Va; - output.write("vorticity cinstant: Upara0 = {:e} Upara1 = {:e}\n", Upara0, Upara1); + Va = sqrt(Bbar * Bbar / (SI::mu0 * Mi * Nbar * density)); - if (diamag) { - Nipara1 = SI::kb * Tibar * eV_K / (Zi * SI::qe * Bbar * Lbar * Va); - Tipara2 = Nipara1; - Tepara2 = SI::kb * Tebar * eV_K / (SI::qe * Bbar * Lbar * Va); - Tepara3 = Bbar / (SI::qe * SI::mu0 * Nbar * density * Lbar * Va); - output.write("Nipara1 = {:e} Tipara2 = {:e}\n", Nipara1, Tipara2); - output.write("Tepara2 = {:e} Tepara3 = {:e}\n", Tepara2, Tepara3); - } + Tbar = Lbar / Va; - if (compress0) { - output.write("Including compression (Vipar) effects\n"); - Vipara = SI::mu0 * SI::kb * Nbar * density * Tebar * eV_K / (Bbar * Bbar); - Vepara = Bbar / (SI::mu0 * Zi * SI::qe * Nbar * density * Lbar * Va); - output.write("Normalized constant for Vipar : Vipara = {:e}\n", Vipara); - output.write("Normalized constant for Vepar : Vepara = {:e}\n", Vepara); - } + output.write("Normalisations: Bbar = {:e} T Lbar = {:e} m\n", Bbar, Lbar); + output.write(" Va = {:e} m/s Tbar = {:e} s\n", Va, Tbar); + output.write(" Nbar = {:e} * {:e} m^-3\n", Nbar, density); + output.write("Tibar = {:e} eV Tebar = {:e} eV Ti/Te = {:e}\n", Tibar, Tebar, + Tau_ie); + output.write(" Resistivity\n"); - if (diffusion_par > 0.0) { - Tipara1 = 2.0 / 3.0 / (Lbar * Va); - Tepara1 = Tipara1 / Zi; - } + Upara0 = SI::kb * Tebar * eV_K / (Zi * SI::qe * Bbar * Va * Lbar); + Upara1 = SI::kb * Tebar * eV_K / Mi / Va / Va; + output.write("vorticity cinstant: Upara0 = {:e} Upara1 = {:e}\n", Upara0, Upara1); - if (vac_lund > 0.0) { - output.write(" Vacuum Tau_R = {:e} s eta = {:e} Ohm m\n", vac_lund * Tbar, - SI::mu0 * Lbar * Lbar / (vac_lund * Tbar)); - vac_resist = 1. / vac_lund; - } else { - output.write(" Vacuum - Zero resistivity -\n"); - vac_resist = 0.0; - } - if (core_lund > 0.0) { - output.write(" Core Tau_R = {:e} s eta = {:e} Ohm m\n", - core_lund * Tbar, SI::mu0 * Lbar * Lbar / (core_lund * Tbar)); - core_resist = 1. / core_lund; - } else { - output.write(" Core - Zero resistivity -\n"); - core_resist = 0.0; - } + if (diamag) { + Nipara1 = SI::kb * Tibar * eV_K / (Zi * SI::qe * Bbar * Lbar * Va); + Tipara2 = Nipara1; + Tepara2 = SI::kb * Tebar * eV_K / (SI::qe * Bbar * Lbar * Va); + Tepara3 = Bbar / (SI::qe * SI::mu0 * Nbar * density * Lbar * Va); + output.write("Nipara1 = {:e} Tipara2 = {:e}\n", Nipara1, Tipara2); + output.write("Tepara2 = {:e} Tepara3 = {:e}\n", Tepara2, Tepara3); + } - if (hyperresist > 0.0) { - output.write(" Hyper-resistivity coefficient: {:e}\n", hyperresist); - dump.add(hyper_eta_x, "hyper_eta_x", 1); - dump.add(hyper_eta_z, "hyper_eta_z", 1); - } + if (compress0) { + output.write("Including compression (Vipar) effects\n"); + Vipara = SI::mu0 * SI::kb * Nbar * density * Tebar * eV_K / (Bbar * Bbar); + Vepara = Bbar / (SI::mu0 * Zi * SI::qe * Nbar * density * Lbar * Va); + output.write("Normalized constant for Vipar : Vipara = {:e}\n", Vipara); + output.write("Normalized constant for Vepar : Vepara = {:e}\n", Vepara); + } - if (ehyperviscos > 0.0) { - output.write(" electron Hyper-viscosity coefficient: {:e}\n", ehyperviscos); - } + if (diffusion_par > 0.0) { + Tipara1 = 2.0 / 3.0 / (Lbar * Va); + Tepara1 = Tipara1 / Zi; + } - if (hyperviscos > 0.0) { - output.write(" Hyper-viscosity coefficient: {:e}\n", hyperviscos); - dump.add(hyper_mu_x, "hyper_mu_x", 1); - } + if (vac_lund > 0.0) { + output.write(" Vacuum Tau_R = {:e} s eta = {:e} Ohm m\n", vac_lund * Tbar, + SI::mu0 * Lbar * Lbar / (vac_lund * Tbar)); + vac_resist = 1. / vac_lund; + } else { + output.write(" Vacuum - Zero resistivity -\n"); + vac_resist = 0.0; + } + if (core_lund > 0.0) { + output.write(" Core Tau_R = {:e} s eta = {:e} Ohm m\n", + core_lund * Tbar, SI::mu0 * Lbar * Lbar / (core_lund * Tbar)); + core_resist = 1. / core_lund; + } else { + output.write(" Core - Zero resistivity -\n"); + core_resist = 0.0; + } - if (diffusion_par > 0.0) { - output.write(" diffusion_par: {:e}\n", diffusion_par); - dump.add(diffusion_par, "diffusion_par", 0); - } + if (hyperresist > 0.0) { + output.write(" Hyper-resistivity coefficient: {:e}\n", hyperresist); + dump.add(hyper_eta_x, "hyper_eta_x", 1); + dump.add(hyper_eta_z, "hyper_eta_z", 1); + } - // 4th order diffusion of p - if (diffusion_n4 > 0.0) { - output.write(" diffusion_n4: {:e}\n", diffusion_n4); - dump.add(diffusion_n4, "diffusion_n4", 0); - } + if (ehyperviscos > 0.0) { + output.write(" electron Hyper-viscosity coefficient: {:e}\n", ehyperviscos); + } - // 4th order diffusion of Ti - if (diffusion_ti4 > 0.0) { - output.write(" diffusion_ti4: {:e}\n", diffusion_ti4); - dump.add(diffusion_ti4, "diffusion_ti4", 0); - } + if (hyperviscos > 0.0) { + output.write(" Hyper-viscosity coefficient: {:e}\n", hyperviscos); + dump.add(hyper_mu_x, "hyper_mu_x", 1); + } - // 4th order diffusion of Te - if (diffusion_te4 > 0.0) { - output.write(" diffusion_te4: {:e}\n", diffusion_te4); - dump.add(diffusion_te4, "diffusion_te4", 0); - } + if (diffusion_par > 0.0) { + output.write(" diffusion_par: {:e}\n", diffusion_par); + dump.add(diffusion_par, "diffusion_par", 0); + } - // 4th order diffusion of Vipar - if (diffusion_v4 > 0.0) { - output.write(" diffusion_v4: {:e}\n", diffusion_v4); - dump.add(diffusion_v4, "diffusion_v4", 0); - } + // 4th order diffusion of p + if (diffusion_n4 > 0.0) { + output.write(" diffusion_n4: {:e}\n", diffusion_n4); + dump.add(diffusion_n4, "diffusion_n4", 0); + } - // parallel hyper-viscous diffusion for vorticity - if (diffusion_u4 > 0.0) { - output.write(" diffusion_u4: {:e}\n", diffusion_u4); - dump.add(diffusion_u4, "diffusion_u4", 0); - } + // 4th order diffusion of Ti + if (diffusion_ti4 > 0.0) { + output.write(" diffusion_ti4: {:e}\n", diffusion_ti4); + dump.add(diffusion_ti4, "diffusion_ti4", 0); + } - if (sink_vp > 0.0) { - output.write(" sink_vp(rate): {:e}\n", sink_vp); - dump.add(sink_vp, "sink_vp", 1); + // 4th order diffusion of Te + if (diffusion_te4 > 0.0) { + output.write(" diffusion_te4: {:e}\n", diffusion_te4); + dump.add(diffusion_te4, "diffusion_te4", 0); + } - output.write(" sp_width(%%): {:e}\n", sp_width); - dump.add(sp_width, "sp_width", 1); + // 4th order diffusion of Vipar + if (diffusion_v4 > 0.0) { + output.write(" diffusion_v4: {:e}\n", diffusion_v4); + dump.add(diffusion_v4, "diffusion_v4", 0); + } - output.write(" sp_length(%%): {:e}\n", sp_length); - dump.add(sp_length, "sp_length", 1); - } + // parallel hyper-viscous diffusion for vorticity + if (diffusion_u4 > 0.0) { + output.write(" diffusion_u4: {:e}\n", diffusion_u4); + dump.add(diffusion_u4, "diffusion_u4", 0); + } - J0 = SI::mu0 * Lbar * J0 / B0; - P0 = P0 / (SI::kb * (Tibar + Tebar) * eV_K / 2. * Nbar * density); - - b0xcv.x /= Bbar; - b0xcv.y *= Lbar * Lbar; - b0xcv.z *= Lbar * Lbar; - - Rxy /= Lbar; - Bpxy /= Bbar; - Btxy /= Bbar; - B0 /= Bbar; - hthe /= Lbar; - coord->setDx(coord->dx() / (Lbar * Lbar * Bbar)); - I *= Lbar * Lbar * Bbar; - - if ((!T0_fake_prof) && n0_fake_prof) { - N0 = N0tanh(n0_height * Nbar, n0_ave * Nbar, n0_width, n0_center, n0_bottom_x); - - Ti0 = P0 / N0 / 2.0; - Te0 = Ti0; - } else if (T0_fake_prof) { - Ti0 = Tconst; - Te0 = Ti0; - N0 = P0 / (Ti0 + Te0); - } else { - if (mesh->get(N0, "Niexp")) { // N_i0 - output_error.write("Error: Cannot read Ni0 from grid\n"); - return 1; - } - - if (mesh->get(Ti0, "Tiexp")) { // T_i0 - output_error.write("Error: Cannot read Ti0 from grid\n"); - return 1; - } - - if (mesh->get(Te0, "Teexp")) { // T_e0 - output_error.write("Error: Cannot read Te0 from grid\n"); - return 1; - } - N0 /= Nbar; - Ti0 /= Tibar; - Te0 /= Tebar; - } + if (sink_vp > 0.0) { + output.write(" sink_vp(rate): {:e}\n", sink_vp); + dump.add(sink_vp, "sink_vp", 1); - Ne0 = Zi * N0; // quasi-neutral condition - Pi0 = N0 * Ti0; - Pe0 = Ne0 * Te0; + output.write(" sp_width(%%): {:e}\n", sp_width); + dump.add(sp_width, "sp_width", 1); - nu_e.setBoundary("kappa"); - if (spitzer_resist) { - eta_spitzer.setBoundary("kappa"); - } - if (diffusion_par > 0.0) { - nu_i.setBoundary("kappa"); - vth_i.setBoundary("kappa"); - vth_e.setBoundary("kappa"); - kappa_par_i.setBoundary("kappa"); - kappa_par_e.setBoundary("kappa"); - kappa_perp_i.setBoundary("kappa"); - kappa_perp_e.setBoundary("kappa"); - } + output.write(" sp_length(%%): {:e}\n", sp_length); + dump.add(sp_length, "sp_length", 1); + } - if (compress0) { - eta_i0.setBoundary("Ti"); - pi_ci.setBoundary("Ti"); - } + auto Bpxy = tokamak_options.Bpxy; + auto hthe = tokamak_options.hthe; + auto Rxy = tokamak_options.Rxy; + auto Btxy = tokamak_options.Btxy; + auto B0 = tokamak_options.Bxy; - BoutReal pnorm = max(P0, true); // Maximum over all processors + J0 = SI::mu0 * Lbar * J0 / B0; + P0 = P0 / (SI::kb * (Tibar + Tebar) * eV_K / 2. * Nbar * density); - vacuum_pressure *= pnorm; // Get pressure from fraction - vacuum_trans *= pnorm; + b0xcv.x /= Bbar; + b0xcv.y *= Lbar * Lbar; + b0xcv.z *= Lbar * Lbar; - // Transitions from 0 in core to 1 in vacuum - vac_mask = (1.0 - tanh((P0 - vacuum_pressure) / vacuum_trans)) / 2.0; + if ((!T0_fake_prof) && n0_fake_prof) { + N0 = N0tanh(n0_height * Nbar, n0_ave * Nbar, n0_width, n0_center, n0_bottom_x); - if (diffusion_par > 0.0) { - if (q95_input > 0) { - q95 = q95_input; // use a constant for test - } else { - if (local_q) { - q95 = abs(hthe * Btxy / (Bpxy)) * q_alpha; + Ti0 = P0 / N0 / 2.0; + Te0 = Ti0; + } else if (T0_fake_prof) { + Ti0 = Tconst; + Te0 = Ti0; + N0 = P0 / (Ti0 + Te0); } else { - output.write("\tUsing q profile from grid.\n"); - if (mesh->get(q95, "q")) { - output.write( - "Cannot get q profile from grid!\nPlease run addqprofile.pro first\n"); - return 1; - } - } - } - output.write("\tlocal max q: {:e}\n", max(q95)); - output.write("\tlocal min q: {:e}\n", min(q95)); - } - - LnLambda = - 24.0 - - log(pow(Zi * Nbar * density / 1.e6, 0.5) * pow(Tebar, -1.0)); // xia: ln Lambda - output.write("\tlog Lambda: {:e}\n", LnLambda); - - nu_e = 2.91e-6 * LnLambda * ((N0)*Nbar * density / 1.e6) - * pow(Te0 * Tebar, -1.5); // nu_e in 1/S. - output.write("\telectron collision rate: {:e} -> {:e} [1/s]\n", min(nu_e), max(nu_e)); - // nu_e.applyBoundary(); - // mesh->communicate(nu_e); - - if (diffusion_par > 0.0) { - - output.write("\tion thermal noramlized constant: Tipara1 = {:e}\n", Tipara1); - output.write("\telectron normalized thermal constant: Tepara1 = {:e}\n", Tepara1); - // xqx addition, begin - // Use Spitzer thermal conductivities - nu_i = 4.80e-8 * (Zi * Zi * Zi * Zi / sqrt(AA)) * LnLambda - * ((N0)*Nbar * density / 1.e6) * pow(Ti0 * Tibar, -1.5); // nu_i in 1/S. - // output.write("\tCoulomb Logarithm: {:e} \n", max(LnLambda)); - output.write("\tion collision rate: {:e} -> {:e} [1/s]\n", min(nu_i), max(nu_i)); - - // nu_i.applyBoundary(); - // mesh->communicate(nu_i); - - vth_i = 9.79e3 * sqrt((Ti0)*Tibar / AA); // vth_i in m/S. - output.write("\tion thermal velocity: {:e} -> {:e} [m/s]\n", min(vth_i), - max(vth_i)); - // vth_i.applyBoundary(); - // mesh->communicate(vth_i); - vth_e = 4.19e5 * sqrt((Te0)*Tebar); // vth_e in m/S. - output.write("\telectron thermal velocity: {:e} -> {:e} [m/s]\n", min(vth_e), - max(vth_e)); - // vth_e.applyBoundary(); - // mesh->communicate(vth_e); - } - - if (compress0) { - eta_i0 = 0.96 * Pi0 * Tau_ie * nu_i * Tbar; - output.write("\tCoefficients of parallel viscocity: {:e} -> {:e} [kg/(m s)]\n", - min(eta_i0), max(eta_i0)); - } + if (mesh->get(N0, "Niexp")) { // N_i0 + output_error.write("Error: Cannot read Ni0 from grid\n"); + return 1; + } - if (diffusion_par > 0.0) { - kappa_par_i = 3.9 * vth_i * vth_i / nu_i; // * 1.e4; - kappa_par_e = 3.2 * vth_e * vth_e / nu_e; // * 1.e4; - - output.write("\tion thermal conductivity: {:e} -> {:e} [m^2/s]\n", min(kappa_par_i), - max(kappa_par_i)); - output.write("\telectron thermal conductivity: {:e} -> {:e} [m^2/s]\n", - min(kappa_par_e), max(kappa_par_e)); - - output.write("\tnormalized ion thermal conductivity: {:e} -> {:e} \n", - min(kappa_par_i * Tipara1), max(kappa_par_i * Tipara1)); - output.write("\tnormalized electron thermal conductivity: {:e} -> {:e} \n", - min(kappa_par_e * Tepara1), max(kappa_par_e * Tepara1)); - - Field3D kappa_par_i_fl, kappa_par_e_fl; - - kappa_par_i_fl = vth_i * (q95 * Lbar); // * 1.e2; - kappa_par_e_fl = vth_e * (q95 * Lbar); // * 1.e2; - - kappa_par_i *= kappa_par_i_fl / (kappa_par_i + kappa_par_i_fl); - kappa_par_i *= Tipara1 * N0; - output.write("\tUsed normalized ion thermal conductivity: {:e} -> {:e} \n", - min(kappa_par_i), max(kappa_par_i)); - // kappa_par_i.applyBoundary(); - // mesh->communicate(kappa_par_i); - kappa_par_e *= kappa_par_e_fl / (kappa_par_e + kappa_par_e_fl); - kappa_par_e *= Tepara1 * N0 / Zi; - output.write("\tUsed normalized electron thermal conductivity: {:e} -> {:e} \n", - min(kappa_par_e), max(kappa_par_e)); - // kappa_par_e.applyBoundary(); - // mesh->communicate(kappa_par_e); - - dump.add(kappa_par_i, "kappa_par_i", 1); - dump.add(kappa_par_e, "kappa_par_e", 1); - } + if (mesh->get(Ti0, "Tiexp")) { // T_i0 + output_error.write("Error: Cannot read Ti0 from grid\n"); + return 1; + } - if (spitzer_resist) { - // Use Spitzer resistivity - output.write("\n\tSpizter parameters"); - // output.write("\tTemperature: {:e} -> {:e} [eV]\n", min(Te), max(Te)); - eta_spitzer = 0.51 * 1.03e-4 * Zi * LnLambda - * pow(Te0 * Tebar, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 - output.write("\tSpitzer resistivity: {:e} -> {:e} [Ohm m]\n", min(eta_spitzer), - max(eta_spitzer)); - eta_spitzer /= SI::mu0 * Va * Lbar; - // eta_spitzer.applyBoundary(); - // mesh->communicate(eta_spitzer); - output.write("\t -> Lundquist {:e} -> {:e}\n", 1.0 / max(eta_spitzer), - 1.0 / min(eta_spitzer)); - dump.add(eta_spitzer, "eta_spitzer", 1); - } else { - // transition from 0 for large P0 to resistivity for small P0 - eta = core_resist + (vac_resist - core_resist) * vac_mask; - eta_spitzer = 0.; - dump.add(eta, "eta", 0); - } + if (mesh->get(Te0, "Teexp")) { // T_e0 + output_error.write("Error: Cannot read Te0 from grid\n"); + return 1; + } + N0 /= Nbar; + Ti0 /= Tibar; + Te0 /= Tebar; + } - /**************** CALCULATE METRICS ******************/ + Ne0 = Zi * N0; // quasi-neutral condition + Pi0 = N0 * Ti0; + Pe0 = Ne0 * Te0; - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(I) * g11 + SQ(B0) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); + nu_e.setBoundary("kappa"); + if (spitzer_resist) { + eta_spitzer.setBoundary("kappa"); + } + if (diffusion_par > 0.0) { + nu_i.setBoundary("kappa"); + vth_i.setBoundary("kappa"); + vth_e.setBoundary("kappa"); + kappa_par_i.setBoundary("kappa"); + kappa_par_e.setBoundary("kappa"); + kappa_perp_i.setBoundary("kappa"); + kappa_perp_e.setBoundary("kappa"); + } - const auto g_11 = 1.0 / g11 + SQ(I * Rxy); - const auto g_22 = SQ(B0 * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; + if (compress0) { + eta_i0.setBoundary("Ti"); + pi_ci.setBoundary("Ti"); + } - coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); + BoutReal pnorm = max(P0, true); // Maximum over all processors + + vacuum_pressure *= pnorm; // Get pressure from fraction + vacuum_trans *= pnorm; + + // Transitions from 0 in core to 1 in vacuum + vac_mask = (1.0 - tanh((P0 - vacuum_pressure) / vacuum_trans)) / 2.0; + + if (diffusion_par > 0.0) { + if (q95_input > 0) { + q95 = q95_input; // use a constant for test + } else { + if (local_q) { + q95 = abs(hthe * Btxy / (Bpxy)) * q_alpha; + } else { + output.write("\tUsing q profile from grid.\n"); + if (mesh->get(q95, "q")) { + output.write( + "Cannot get q profile from grid!\nPlease run addqprofile.pro first\n"); + return 1; + } + } + } + output.write("\tlocal max q: {:e}\n", max(q95)); + output.write("\tlocal min q: {:e}\n", min(q95)); + } - coord->setJ(hthe / Bpxy); - coord->setBxy(B0); + LnLambda = + 24.0 + - log(pow(Zi * Nbar * density / 1.e6, 0.5) * pow(Tebar, -1.0)); // xia: ln Lambda + output.write("\tlog Lambda: {:e}\n", LnLambda); + + nu_e = 2.91e-6 * LnLambda * ((N0) * Nbar * density / 1.e6) + * pow(Te0 * Tebar, -1.5); // nu_e in 1/S. + output.write("\telectron collision rate: {:e} -> {:e} [1/s]\n", min(nu_e), max(nu_e)); + // nu_e.applyBoundary(); + // mesh->communicate(nu_e); + + if (diffusion_par > 0.0) { + + output.write("\tion thermal noramlized constant: Tipara1 = {:e}\n", Tipara1); + output.write("\telectron normalized thermal constant: Tepara1 = {:e}\n", Tepara1); + // xqx addition, begin + // Use Spitzer thermal conductivities + nu_i = 4.80e-8 * (Zi * Zi * Zi * Zi / sqrt(AA)) * LnLambda + * ((N0) * Nbar * density / 1.e6) * pow(Ti0 * Tibar, -1.5); // nu_i in 1/S. + // output.write("\tCoulomb Logarithm: {:e} \n", max(LnLambda)); + output.write("\tion collision rate: {:e} -> {:e} [1/s]\n", min(nu_i), max(nu_i)); + + // nu_i.applyBoundary(); + // mesh->communicate(nu_i); + + vth_i = 9.79e3 * sqrt((Ti0) * Tibar / AA); // vth_i in m/S. + output.write("\tion thermal velocity: {:e} -> {:e} [m/s]\n", min(vth_i), + max(vth_i)); + // vth_i.applyBoundary(); + // mesh->communicate(vth_i); + vth_e = 4.19e5 * sqrt((Te0) * Tebar); // vth_e in m/S. + output.write("\telectron thermal velocity: {:e} -> {:e} [m/s]\n", min(vth_e), + max(vth_e)); + // vth_e.applyBoundary(); + // mesh->communicate(vth_e); + } - // Set B field vector + if (compress0) { + eta_i0 = 0.96 * Pi0 * Tau_ie * nu_i * Tbar; + output.write("\tCoefficients of parallel viscocity: {:e} -> {:e} [kg/(m s)]\n", + min(eta_i0), max(eta_i0)); + } - B0vec.covariant = false; - B0vec.x = 0.; - B0vec.y = Bpxy / hthe; - B0vec.z = 0.; + if (diffusion_par > 0.0) { + kappa_par_i = 3.9 * vth_i * vth_i / nu_i; // * 1.e4; + kappa_par_e = 3.2 * vth_e * vth_e / nu_e; // * 1.e4; + + output.write("\tion thermal conductivity: {:e} -> {:e} [m^2/s]\n", min(kappa_par_i), + max(kappa_par_i)); + output.write("\telectron thermal conductivity: {:e} -> {:e} [m^2/s]\n", + min(kappa_par_e), max(kappa_par_e)); + + output.write("\tnormalized ion thermal conductivity: {:e} -> {:e} \n", + min(kappa_par_i * Tipara1), max(kappa_par_i * Tipara1)); + output.write("\tnormalized electron thermal conductivity: {:e} -> {:e} \n", + min(kappa_par_e * Tepara1), max(kappa_par_e * Tepara1)); + + Field3D kappa_par_i_fl, kappa_par_e_fl; + + kappa_par_i_fl = vth_i * (q95 * Lbar); // * 1.e2; + kappa_par_e_fl = vth_e * (q95 * Lbar); // * 1.e2; + + kappa_par_i *= kappa_par_i_fl / (kappa_par_i + kappa_par_i_fl); + kappa_par_i *= Tipara1 * N0; + output.write("\tUsed normalized ion thermal conductivity: {:e} -> {:e} \n", + min(kappa_par_i), max(kappa_par_i)); + // kappa_par_i.applyBoundary(); + // mesh->communicate(kappa_par_i); + kappa_par_e *= kappa_par_e_fl / (kappa_par_e + kappa_par_e_fl); + kappa_par_e *= Tepara1 * N0 / Zi; + output.write("\tUsed normalized electron thermal conductivity: {:e} -> {:e} \n", + min(kappa_par_e), max(kappa_par_e)); + // kappa_par_e.applyBoundary(); + // mesh->communicate(kappa_par_e); + + dump.add(kappa_par_i, "kappa_par_i", 1); + dump.add(kappa_par_e, "kappa_par_e", 1); + } - // Set V0vec field vector + if (spitzer_resist) { + // Use Spitzer resistivity + output.write("\n\tSpizter parameters"); + // output.write("\tTemperature: {:e} -> {:e} [eV]\n", min(Te), max(Te)); + eta_spitzer = 0.51 * 1.03e-4 * Zi * LnLambda + * pow(Te0 * Tebar, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 + output.write("\tSpitzer resistivity: {:e} -> {:e} [Ohm m]\n", min(eta_spitzer), + max(eta_spitzer)); + eta_spitzer /= SI::mu0 * Va * Lbar; + // eta_spitzer.applyBoundary(); + // mesh->communicate(eta_spitzer); + output.write("\t -> Lundquist {:e} -> {:e}\n", 1.0 / max(eta_spitzer), + 1.0 / min(eta_spitzer)); + dump.add(eta_spitzer, "eta_spitzer", 1); + } else { + // transition from 0 for large P0 to resistivity for small P0 + eta = core_resist + (vac_resist - core_resist) * vac_mask; + eta_spitzer = 0.; + dump.add(eta, "eta", 0); + } - V0vec.covariant = false; - V0vec.x = 0.; - V0vec.y = Vp0 / hthe; - V0vec.z = Vt0 / Rxy; + /**************** CALCULATE METRICS ******************/ - // Set V0eff field vector + // Set B field vector - V0eff.covariant = false; - V0eff.x = 0.; - V0eff.y = -(Btxy / (B0 * B0)) * (Vp0 * Btxy - Vt0 * Bpxy) / hthe; - V0eff.z = (Bpxy / (B0 * B0)) * (Vp0 * Btxy - Vt0 * Bpxy) / Rxy; + B0vec.covariant = false; + B0vec.x = 0.; + B0vec.y = Bpxy / hthe; + B0vec.z = 0.; - Pe.setBoundary("P"); - Pi.setBoundary("P"); + // Set V0vec field vector - /**************** SET EVOLVING VARIABLES *************/ + V0vec.covariant = false; + V0vec.x = 0.; + V0vec.y = Vp0 / hthe; + V0vec.z = Vt0 / Rxy; - // Tell BOUT which variables to evolve - SOLVE_FOR(U, Ni, Ti, Te, Psi); + // Set V0eff field vector - SAVE_REPEAT(Jpar, P, Vepar); + V0eff.covariant = false; + V0eff.x = 0.; + V0eff.y = -(Btxy / (B0 * B0)) * (Vp0 * Btxy - Vt0 * Bpxy) / hthe; + V0eff.z = (Bpxy / (B0 * B0)) * (Vp0 * Btxy - Vt0 * Bpxy) / Rxy; - if (parallel_lagrange) { - // Evolving the distortion of the flux surfaces (Ideal-MHD only!) - SOLVE_FOR(Xip_x, Xip_z, Xim_x, Xim_z); - } + Pe.setBoundary("P"); + Pi.setBoundary("P"); - if (parallel_project) { - // Add Xi to the dump file - SAVE_REPEAT(Xip_x, Xip_z, Xim_x, Xim_z); - } + /**************** SET EVOLVING VARIABLES *************/ - if (compress0) { - SOLVE_FOR(Vipar); - if (!restarting) { - Vipar = 0.0; - } - } + // Tell BOUT which variables to evolve + SOLVE_FOR(U, Ni, Ti, Te, Psi); - if (phi_constraint) { - // Implicit Phi solve using IDA - solver->constraint(phi, C_phi, "phi"); + SAVE_REPEAT(Jpar, P, Vepar); - } else { - // Phi solved in RHS (explicitly) - SAVE_REPEAT(phi); - } + if (parallel_lagrange) { + // Evolving the distortion of the flux surfaces (Ideal-MHD only!) + SOLVE_FOR(Xip_x, Xip_z, Xim_x, Xim_z); + } - // Diamagnetic phi0 - if (diamag && diamag_phi0) { - if (experiment_Er) { // get phi0 from grid file - mesh->get(phi0, "Phi_0"); - phi0 /= B0 * Lbar * Va; - } else { - // Stationary equilibrium plasma. ExB velocity balances diamagnetic drift - phi0 = -Upara0 * Pi0 / B0 / N0; - } - SAVE_ONCE(phi0); - } + if (parallel_project) { + // Add Xi to the dump file + SAVE_REPEAT(Xip_x, Xip_z, Xim_x, Xim_z); + } - // Add some equilibrium quantities and normalisations - // everything needed to recover physical units - SAVE_ONCE(J0, P0); - SAVE_ONCE(density, Lbar, Bbar, Tbar); - SAVE_ONCE(Tibar, Tebar, Nbar); - SAVE_ONCE(Va, B0); - SAVE_ONCE(Ti0, Te0, N0); + if (compress0) { + SOLVE_FOR(Vipar); + if (!restarting) { + Vipar = 0.0; + } + } - // Create a solver for the Laplacian - phiSolver = Laplacian::create(&globalOptions["phiSolver"]); + if (phi_constraint) { + // Implicit Phi solve using IDA + solver->constraint(phi, C_phi, "phi"); - aparSolver = Laplacian::create(&globalOptions["aparSolver"]); + } else { + // Phi solved in RHS (explicitly) + SAVE_REPEAT(phi); + } - /////////////// CHECK VACUUM /////////////////////// - // In vacuum region, initial vorticity should equal zero + // Diamagnetic phi0 + if (diamag && diamag_phi0) { + if (experiment_Er) { // get phi0 from grid file + mesh->get(phi0, "Phi_0"); + phi0 /= B0 * Lbar * Va; + } else { + // Stationary equilibrium plasma. ExB velocity balances diamagnetic drift + phi0 = -Upara0 * Pi0 / B0 / N0; + } + SAVE_ONCE(phi0); + } - ubyn.setBoundary("U"); + // Add some equilibrium quantities and normalisations + // everything needed to recover physical units + SAVE_ONCE(J0, P0); + SAVE_ONCE(density, Lbar, Bbar, Tbar); + SAVE_ONCE(Tibar, Tebar, Nbar); + SAVE_ONCE(Va, B0); + SAVE_ONCE(Ti0, Te0, N0); + + // Create a solver for the Laplacian + phiSolver = Laplacian::create(&globalOptions["phiSolver"]); + + aparSolver = Laplacian::create(&globalOptions["aparSolver"]); + + /////////////// CHECK VACUUM /////////////////////// + // In vacuum region, initial vorticity should equal zero + + ubyn.setBoundary("U"); + + if (!restarting) { + // Only if not restarting: Check initial perturbation + + // Set U to zero where P0 < vacuum_pressure + U = where(P0 - vacuum_pressure, U, 0.0); + + // Field2D lap_temp = 0.0; + Field2D logn0 = laplace_alpha * N0; + Field3D Ntemp; + Ntemp = N0; + ubyn = U * B0 / Ntemp; + // Phi should be consistent with U + if (laplace_alpha <= 0.0) { + phi = phiSolver->solve(ubyn) / B0; + } else { + phiSolver->setCoefC(logn0); + phi = phiSolver->solve(ubyn) / B0; + } + } - if (!restarting) { - // Only if not restarting: Check initial perturbation + /************** SETUP COMMUNICATIONS **************/ - // Set U to zero where P0 < vacuum_pressure - U = where(P0 - vacuum_pressure, U, 0.0); + comms.add(U); + comms.add(Ni); + comms.add(Ti); + comms.add(Te); + if (!emass) { + comms.add(Psi); + } else { + comms.add(Ajpar); + } - // Field2D lap_temp = 0.0; - Field2D logn0 = laplace_alpha * N0; - Field3D Ntemp; - Ntemp = N0; - ubyn = U * B0 / Ntemp; - // Phi should be consistent with U - if (laplace_alpha <= 0.0) { - phi = phiSolver->solve(ubyn) / B0; - } else { - phiSolver->setCoefC(logn0); - phi = phiSolver->solve(ubyn) / B0; - } - } + if (compress0) { + comms.add(Vipar); + Vepar.setBoundary("Vipar"); + } - /************** SETUP COMMUNICATIONS **************/ + if (diffusion_u4 > 0.0) { + tmpA2.setBoundary("J"); + } - comms.add(U); - comms.add(Ni); - comms.add(Ti); - comms.add(Te); - if (!emass) { - comms.add(Psi); - } else { - comms.add(Ajpar); - } + if (diffusion_n4 > 0.0) { + tmpN2.setBoundary("Ni"); + } - if (compress0) { - comms.add(Vipar); - Vepar.setBoundary("Vipar"); - } + if (diffusion_ti4 > 0.0) { + tmpTi2.setBoundary("Ti"); + } - if (diffusion_u4 > 0.0) { - tmpA2.setBoundary("J"); - } + if (diffusion_te4 > 0.0) { + tmpTe2.setBoundary("Te"); + } - if (diffusion_n4 > 0.0) { - tmpN2.setBoundary("Ni"); - } + if (diffusion_v4 > 0.0) { + tmpVp2.setBoundary("Vipar"); + } - if (diffusion_ti4 > 0.0) { - tmpTi2.setBoundary("Ti"); - } + phi.setBoundary("phi"); // Set boundary conditions - if (diffusion_te4 > 0.0) { - tmpTe2.setBoundary("Te"); - } + P.setBoundary("P"); + Jpar.setBoundary("J"); + Jpar2.setBoundary("J"); - if (diffusion_v4 > 0.0) { - tmpVp2.setBoundary("Vipar"); + return 0; } - phi.setBoundary("phi"); // Set boundary conditions + int rhs(BoutReal UNUSED(t)) override { - P.setBoundary("P"); - Jpar.setBoundary("J"); - Jpar2.setBoundary("J"); + Coordinates *coord = mesh->getCoordinates(); - return 0; - } - int rhs(BoutReal UNUSED(t)) override { + // Perform communications + mesh->communicate(comms); - Coordinates* coord = mesh->getCoordinates(); + // Inversion + Pi = Ni * Ti0 + N0 * Ti; + if (nonlinear) { + Pi += Ni * Ti; + } + mesh->communicate(Pi); - // Perform communications - mesh->communicate(comms); + Pe = Zi * (Ni * Te0 + N0 * Te); + if (nonlinear) { + Pe += Zi * Ni * Te; + } + mesh->communicate(Pe); - // Inversion - Pi = Ni * Ti0 + N0 * Ti; - if (nonlinear) { - Pi += Ni * Ti; - } - mesh->communicate(Pi); + P = Tau_ie * Pi + Pe; + mesh->communicate(P); - Pe = Zi * (Ni * Te0 + N0 * Te); - if (nonlinear) { - Pe += Zi * Ni * Te; - } - mesh->communicate(Pe); - - P = Tau_ie * Pi + Pe; - mesh->communicate(P); - - // Field2D lap_temp=0.0; - Field2D logn0 = laplace_alpha * N0; - ubyn = U * B0 / N0; - if (diamag) { - ubyn -= Upara0 / N0 * Delp2(Pi) / B0; - mesh->communicate(ubyn); - ubyn.applyBoundary(); - } - // Invert laplacian for phi - if (laplace_alpha > 0.0) { - phiSolver->setCoefC(logn0); - } - phi = phiSolver->solve(ubyn) / B0; - - mesh->communicate(phi); - - if (emass) { - Field2D acoeff = -delta_e_inv * N0 * N0; - if (compress0) { - Psi = aparSolver->solve(acoeff * Ajpar - gyroAlv * Vipar); - } else { - Psi = aparSolver->solve(acoeff * Ajpar); - } - mesh->communicate(Psi); - } + // Field2D lap_temp=0.0; + Field2D logn0 = laplace_alpha * N0; - BoutReal N_tmp1; - N_tmp1 = Low_limit; - N_tmp = field_larger(N0 + Ni, N_tmp1); - - BoutReal Te_tmp1, Ti_tmp1; - Te_tmp1 = Low_limit; - Ti_tmp1 = Low_limit; - - Ti_tmp = field_larger(Ti0 + Ti, Ti_tmp1); - Te_tmp = field_larger(Te0 + Te, Te_tmp1); - - // vac_mask transitions from 0 in core to 1 in vacuum - if (nonlinear) { - vac_mask = (1.0 - tanh(((P0 + P) - vacuum_pressure) / vacuum_trans)) / 2.0; - // Update resistivity - if (spitzer_resist) { - // Use Spitzer formula - eta_spitzer = 0.51 * 1.03e-4 * Zi * LnLambda - * pow(Te_tmp * Tebar, -1.5); // eta in Ohm-m. ln(Lambda) = 20 - eta_spitzer /= SI::mu0 * Va * Lbar; - } else { - eta = core_resist + (vac_resist - core_resist) * vac_mask; - } - - nu_e = 2.91e-6 * LnLambda * (N_tmp * Nbar * density / 1.e6) - * pow(Te_tmp * Tebar, -1.5); // nu_e in 1/S. - - if (diffusion_par > 0.0) { - // Use Spitzer thermal conductivities - - nu_i = 4.80e-8 * (Zi * Zi * Zi * Zi / sqrt(AA)) * LnLambda - * (N_tmp * Nbar * density / 1.e6) - * pow(Ti_tmp * Tibar, -1.5); // nu_i in 1/S. - vth_i = 9.79e3 * sqrt(Ti_tmp * Tibar / AA); // vth_i in m/S. - vth_e = 4.19e5 * sqrt(Te_tmp * Tebar); // vth_e in m/S. - } - - if (diffusion_par > 0.0) { - kappa_par_i = 3.9 * vth_i * vth_i / nu_i; // * 1.e4; - kappa_par_e = 3.2 * vth_e * vth_e / nu_e; // * 1.e4; - - Field3D kappa_par_i_fl, kappa_par_e_fl; - - kappa_par_i_fl = vth_i * (q95 * Lbar); // * 1.e2; - kappa_par_e_fl = vth_e * (q95 * Lbar); // * 1.e2; - - kappa_par_i *= kappa_par_i_fl / (kappa_par_i + kappa_par_i_fl); - kappa_par_i *= Tipara1 * N_tmp; - kappa_par_e *= kappa_par_e_fl / (kappa_par_e + kappa_par_e_fl); - kappa_par_e *= Tepara1 * N_tmp * Zi; - } - } + auto B0 = tokamak_options.Bxy; - Jpar = -Delp2(Psi); - Jpar.applyBoundary(); - mesh->communicate(Jpar); + ubyn = U * B0 / N0; + if (diamag) { + ubyn -= Upara0 / N0 * Delp2(Pi) / B0; + mesh->communicate(ubyn); + ubyn.applyBoundary(); + } + // Invert laplacian for phi + if (laplace_alpha > 0.0) { + phiSolver->setCoefC(logn0); + } + phi = phiSolver->solve(ubyn) / B0; - if (jpar_bndry_width > 0) { - // Zero j in boundary regions. Prevents vorticity drive - // at the boundary + mesh->communicate(phi); - for (int i = 0; i < jpar_bndry_width; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - if (mesh->firstX()) { - Jpar(i, j, k) = 0.0; + if (emass) { + Field2D acoeff = -delta_e_inv * N0 * N0; + if (compress0) { + Psi = aparSolver->solve(acoeff * Ajpar - gyroAlv * Vipar); + } else { + Psi = aparSolver->solve(acoeff * Ajpar); } - if (mesh->lastX()) { - Jpar(mesh->LocalNx - 1 - i, j, k) = 0.0; - } - } + mesh->communicate(Psi); } - } - } - // Smooth j in x - if (smooth_j_x) { - Jpar = smooth_x(Jpar); - } + BoutReal N_tmp1; + N_tmp1 = Low_limit; + N_tmp = field_larger(N0 + Ni, N_tmp1); + + BoutReal Te_tmp1, Ti_tmp1; + Te_tmp1 = Low_limit; + Ti_tmp1 = Low_limit; + + Ti_tmp = field_larger(Ti0 + Ti, Ti_tmp1); + Te_tmp = field_larger(Te0 + Te, Te_tmp1); + + // vac_mask transitions from 0 in core to 1 in vacuum + if (nonlinear) { + vac_mask = (1.0 - tanh(((P0 + P) - vacuum_pressure) / vacuum_trans)) / 2.0; + // Update resistivity + if (spitzer_resist) { + // Use Spitzer formula + eta_spitzer = 0.51 * 1.03e-4 * Zi * LnLambda + * pow(Te_tmp * Tebar, -1.5); // eta in Ohm-m. ln(Lambda) = 20 + eta_spitzer /= SI::mu0 * Va * Lbar; + } else { + eta = core_resist + (vac_resist - core_resist) * vac_mask; + } - if (compress0) { - if (nonlinear) { - Vepar = Vipar - B0 * (Jpar) / N_tmp * Vepara; - } else { - Vepar = Vipar - B0 * (Jpar) / N0 * Vepara; - Vepar.applyBoundary(); - mesh->communicate(Vepar); - } - } + nu_e = 2.91e-6 * LnLambda * (N_tmp * Nbar * density / 1.e6) + * pow(Te_tmp * Tebar, -1.5); // nu_e in 1/S. - // Get Delp2(J) from J - Jpar2 = -Delp2(Jpar); + if (diffusion_par > 0.0) { + // Use Spitzer thermal conductivities - Jpar2.applyBoundary(); - mesh->communicate(Jpar2); + nu_i = 4.80e-8 * (Zi * Zi * Zi * Zi / sqrt(AA)) * LnLambda + * (N_tmp * Nbar * density / 1.e6) + * pow(Ti_tmp * Tibar, -1.5); // nu_i in 1/S. + vth_i = 9.79e3 * sqrt(Ti_tmp * Tibar / AA); // vth_i in m/S. + vth_e = 4.19e5 * sqrt(Te_tmp * Tebar); // vth_e in m/S. + } + + if (diffusion_par > 0.0) { + kappa_par_i = 3.9 * vth_i * vth_i / nu_i; // * 1.e4; + kappa_par_e = 3.2 * vth_e * vth_e / nu_e; // * 1.e4; - if (jpar_bndry_width > 0) { - // Zero jpar2 in boundary regions. Prevents vorticity drive - // at the boundary + Field3D kappa_par_i_fl, kappa_par_e_fl; - for (int i = 0; i < jpar_bndry_width; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - if (mesh->firstX()) { - Jpar2(i, j, k) = 0.0; + kappa_par_i_fl = vth_i * (q95 * Lbar); // * 1.e2; + kappa_par_e_fl = vth_e * (q95 * Lbar); // * 1.e2; + + kappa_par_i *= kappa_par_i_fl / (kappa_par_i + kappa_par_i_fl); + kappa_par_i *= Tipara1 * N_tmp; + kappa_par_e *= kappa_par_e_fl / (kappa_par_e + kappa_par_e_fl); + kappa_par_e *= Tepara1 * N_tmp * Zi; } - if (mesh->lastX()) { - Jpar2(mesh->LocalNx - 1 - i, j, k) = 0.0; + } + + Jpar = -Delp2(Psi); + Jpar.applyBoundary(); + mesh->communicate(Jpar); + + if (jpar_bndry_width > 0) { + // Zero j in boundary regions. Prevents vorticity drive + // at the boundary + + for (int i = 0; i < jpar_bndry_width; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + if (mesh->firstX()) { + Jpar(i, j, k) = 0.0; + } + if (mesh->lastX()) { + Jpar(mesh->LocalNx - 1 - i, j, k) = 0.0; + } + } + } } - } } - } - } - //////////////////////////////////////////////////// - // Parallel electric field - { - TRACE("ddt(Psi)"); + // Smooth j in x + if (smooth_j_x) { + Jpar = smooth_x(Jpar); + } - ddt(Psi) = 0.0; + if (compress0) { + if (nonlinear) { + Vepar = Vipar - B0 * (Jpar) / N_tmp * Vepara; + } else { + Vepar = Vipar - B0 * (Jpar) / N0 * Vepara; + Vepar.applyBoundary(); + mesh->communicate(Vepar); + } + } - if (spitzer_resist) { - ddt(Psi) = -Grad_parP(B0 * phi) / B0 - eta_spitzer * Jpar; - } else { - ddt(Psi) = -Grad_parP(B0 * phi) / B0 - eta * Jpar; - } + // Get Delp2(J) from J + Jpar2 = -Delp2(Jpar); + + Jpar2.applyBoundary(); + mesh->communicate(Jpar2); + + if (jpar_bndry_width > 0) { + // Zero jpar2 in boundary regions. Prevents vorticity drive + // at the boundary + + for (int i = 0; i < jpar_bndry_width; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + if (mesh->firstX()) { + Jpar2(i, j, k) = 0.0; + } + if (mesh->lastX()) { + Jpar2(mesh->LocalNx - 1 - i, j, k) = 0.0; + } + } + } + } + } - if (diamag) { - ddt(Psi) -= bracket(B0 * phi0, Psi, bm_exb); // Equilibrium flow - } + //////////////////////////////////////////////////// + // Parallel electric field + { + TRACE("ddt(Psi)"); - // Hyper-resistivity - if (hyperresist > 0.0) { - ddt(Psi) += hyperresist * Delp2(Jpar); - } - } + ddt(Psi) = 0.0; - //////////////////////////////////////////////////// - // Vorticity equation + if (spitzer_resist) { + ddt(Psi) = -Grad_parP(B0 * phi) / B0 - eta_spitzer * Jpar; + } else { + ddt(Psi) = -Grad_parP(B0 * phi) / B0 - eta * Jpar; + } - { - TRACE("ddt(U)"); + if (diamag) { + ddt(Psi) -= bracket(B0 * phi0, Psi, bm_exb); // Equilibrium flow + } - ddt(U) = 0.0; + // Hyper-resistivity + if (hyperresist > 0.0) { + ddt(Psi) += hyperresist * Delp2(Jpar); + } + } - ddt(U) = -SQ(B0) * bracket(Psi, J0, bm_mag) * B0; // Grad j term + //////////////////////////////////////////////////// + // Vorticity equation - ddt(U) += 2.0 * Upara1 * b0xcv * Grad(P); // curvature term + { + TRACE("ddt(U)"); - ddt(U) += SQ(B0) * Grad_parP(Jpar); // b dot grad j + ddt(U) = 0.0; - if (diamag) { - ddt(U) -= bracket(B0 * phi0, U, bm_exb); // Equilibrium flow - } + ddt(U) = -SQ(B0) * bracket(Psi, J0, bm_mag) * B0; // Grad j term - if (nonlinear) { - ddt(U) -= bracket(B0 * phi, U, bm_exb); // Advection - } + ddt(U) += 2.0 * Upara1 * b0xcv * Grad(P); // curvature term - // parallel hyper-viscous diffusion for vector potential - if (diffusion_u4 > 0.0) { - tmpA2 = Grad2_par2new(Psi); - mesh->communicate(tmpA2); - tmpA2.applyBoundary(); - ddt(U) -= diffusion_u4 * Grad2_par2new(tmpA2); - } + ddt(U) += SQ(B0) * Grad_parP(Jpar); // b dot grad j - // Viscosity terms - if (viscos_par > 0.0) { - ddt(U) += viscos_par * Grad2_par2(U); // Parallel viscosity - } + if (diamag) { + ddt(U) -= bracket(B0 * phi0, U, bm_exb); // Equilibrium flow + } - if (hyperviscos > 0.0) { - // Calculate coefficient. + if (nonlinear) { + ddt(U) -= bracket(B0 * phi, U, bm_exb); // Advection + } - hyper_mu_x = hyperviscos * coord->g_11() * SQ(coord->dx()) - * abs(coord->g11() * D2DX2(U)) / (abs(U) + 1e-3); - hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries + // parallel hyper-viscous diffusion for vector potential + if (diffusion_u4 > 0.0) { + tmpA2 = Grad2_par2new(Psi); + mesh->communicate(tmpA2); + tmpA2.applyBoundary(); + ddt(U) -= diffusion_u4 * Grad2_par2new(tmpA2); + } - ddt(U) += hyper_mu_x * coord->g11() * D2DX2(U); + // Viscosity terms + if (viscos_par > 0.0) { + ddt(U) += viscos_par * Grad2_par2(U); // Parallel viscosity + } - if (first_run) { - // Print out maximum values of viscosity used on this processor - output.write(" Hyper-viscosity values:\n"); - output.write(" Max mu_x = {:e}, Max_DC mu_x = {:e}\n", max(hyper_mu_x), - max(DC(hyper_mu_x))); - } - } + if (hyperviscos > 0.0) { + // Calculate coefficient. - // left edge sink terms - if (sink_Ul > 0.0) { - ddt(U) -= sink_Ul * sink_tanhxl(P0, U, su_widthl, su_lengthl); // core sink - } + hyper_mu_x = hyperviscos * coord->g_11() * SQ(coord->dx()) + * abs(coord->g11() * D2DX2(U)) / (abs(U) + 1e-3); + hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries - // right edge sink terms - if (sink_Ur > 0.0) { - ddt(U) -= sink_Ur * sink_tanhxr(P0, U, su_widthr, su_lengthr); // sol sink - } - } + ddt(U) += hyper_mu_x * coord->g11() * D2DX2(U); - /////////////////////////////////////////////// - // number density equation + if (first_run) { + // Print out maximum values of viscosity used on this processor + output.write(" Hyper-viscosity values:\n"); + output.write(" Max mu_x = {:e}, Max_DC mu_x = {:e}\n", max(hyper_mu_x), + max(DC(hyper_mu_x))); + } + } - { - TRACE("ddt(Ni)"); + // left edge sink terms + if (sink_Ul > 0.0) { + ddt(U) -= sink_Ul * sink_tanhxl(P0, U, su_widthl, su_lengthl); // core sink + } - ddt(Ni) = 0.0; + // right edge sink terms + if (sink_Ur > 0.0) { + ddt(U) -= sink_Ur * sink_tanhxr(P0, U, su_widthr, su_lengthr); // sol sink + } + } - ddt(Ni) -= bracket(B0 * phi, N0, bm_exb); + /////////////////////////////////////////////// + // number density equation - if (diamag) { - ddt(Ni) -= bracket(B0 * phi0, Ni, bm_exb); // Equilibrium flow - } + { + TRACE("ddt(Ni)"); - if (nonlinear) { - ddt(Ni) -= bracket(B0 * phi, Ni, bm_exb); // Advection - } + ddt(Ni) = 0.0; - if (compress0) { - ddt(Ni) -= N0 * B0 * Grad_parP(Vipar / B0); - } + ddt(Ni) -= bracket(B0 * phi, N0, bm_exb); - // 4th order Parallel diffusion terms - if (diffusion_n4 > 0.0) { - tmpN2 = Grad2_par2new(Ni); - mesh->communicate(tmpN2); - tmpN2.applyBoundary(); - ddt(Ni) -= diffusion_n4 * Grad2_par2new(tmpN2); - } - } + if (diamag) { + ddt(Ni) -= bracket(B0 * phi0, Ni, bm_exb); // Equilibrium flow + } + + if (nonlinear) { + ddt(Ni) -= bracket(B0 * phi, Ni, bm_exb); // Advection + } - /////////////////////////////////////////////// - // ion temperature equation - { - TRACE("ddt(Ti)"); + if (compress0) { + ddt(Ni) -= N0 * B0 * Grad_parP(Vipar / B0); + } - ddt(Ti) = 0.0; + // 4th order Parallel diffusion terms + if (diffusion_n4 > 0.0) { + tmpN2 = Grad2_par2new(Ni); + mesh->communicate(tmpN2); + tmpN2.applyBoundary(); + ddt(Ni) -= diffusion_n4 * Grad2_par2new(tmpN2); + } + } - ddt(Ti) -= bracket(B0 * phi, Ti0, bm_exb); + /////////////////////////////////////////////// + // ion temperature equation + { + TRACE("ddt(Ti)"); - if (diamag) { - ddt(Ti) -= bracket(phi0 * B0, Ti, bm_exb); // Equilibrium flow - } + ddt(Ti) = 0.0; - if (nonlinear) { - ddt(Ti) -= bracket(phi * B0, Ti, bm_exb); // Advection - } + ddt(Ti) -= bracket(B0 * phi, Ti0, bm_exb); - if (compress0) { - ddt(Ti) -= 2.0 / 3.0 * Ti0 * B0 * Grad_parP(Vipar / B0); - } + if (diamag) { + ddt(Ti) -= bracket(phi0 * B0, Ti, bm_exb); // Equilibrium flow + } - if (diffusion_par > 0.0) { - ddt(Ti) += kappa_par_i * Grad2_par2(Ti) / N0; // Parallel diffusion - ddt(Ti) += Grad_par(kappa_par_i) * Grad_par(Ti) / N0; - } + if (nonlinear) { + ddt(Ti) -= bracket(phi * B0, Ti, bm_exb); // Advection + } - // 4th order Parallel diffusion terms - if (diffusion_ti4 > 0.0) { - tmpTi2 = Grad2_par2new(Ti); - mesh->communicate(tmpTi2); - tmpTi2.applyBoundary(); - ddt(Ti) -= diffusion_ti4 * Grad2_par2new(tmpTi2); - } - } + if (compress0) { + ddt(Ti) -= 2.0 / 3.0 * Ti0 * B0 * Grad_parP(Vipar / B0); + } - /////////////////////////////////////////////// - // electron temperature equation + if (diffusion_par > 0.0) { + ddt(Ti) += kappa_par_i * Grad2_par2(Ti) / N0; // Parallel diffusion + ddt(Ti) += Grad_par(kappa_par_i) * Grad_par(Ti) / N0; + } - { - TRACE("ddt(Te)"); + // 4th order Parallel diffusion terms + if (diffusion_ti4 > 0.0) { + tmpTi2 = Grad2_par2new(Ti); + mesh->communicate(tmpTi2); + tmpTi2.applyBoundary(); + ddt(Ti) -= diffusion_ti4 * Grad2_par2new(tmpTi2); + } + } - ddt(Te) = 0.0; + /////////////////////////////////////////////// + // electron temperature equation - ddt(Te) -= bracket(B0 * phi, Te0, bm_exb); + { + TRACE("ddt(Te)"); - if (diamag) { - ddt(Te) -= bracket(B0 * phi0, Te, bm_exb); // Equilibrium flow - } + ddt(Te) = 0.0; - if (nonlinear) { - ddt(Te) -= bracket(B0 * phi, Te, bm_exb); // Advection - } + ddt(Te) -= bracket(B0 * phi, Te0, bm_exb); - if (compress0) { - ddt(Te) -= 2.0 / 3.0 * Te0 * B0 * Grad_parP(Vepar / B0); - } + if (diamag) { + ddt(Te) -= bracket(B0 * phi0, Te, bm_exb); // Equilibrium flow + } - if (diffusion_par > 0.0) { - ddt(Te) += kappa_par_e * Grad2_par2(Te) / N0; // Parallel diffusion - ddt(Te) += Grad_par(kappa_par_e) * Grad_par(Te) / N0; - } + if (nonlinear) { + ddt(Te) -= bracket(B0 * phi, Te, bm_exb); // Advection + } - if (diffusion_te4 > 0.0) { - tmpTe2 = Grad2_par2new(Te); - mesh->communicate(tmpTe2); - tmpTe2.applyBoundary(); - ddt(Te) -= diffusion_te4 * Grad2_par2new(tmpTe2); - } - } + if (compress0) { + ddt(Te) -= 2.0 / 3.0 * Te0 * B0 * Grad_parP(Vepar / B0); + } - ////////////////////////////////////////////////////////////////////// - if (compress0) { // parallel velocity equation - TRACE("ddt(Vipar)"); + if (diffusion_par > 0.0) { + ddt(Te) += kappa_par_e * Grad2_par2(Te) / N0; // Parallel diffusion + ddt(Te) += Grad_par(kappa_par_e) * Grad_par(Te) / N0; + } - ddt(Vipar) = 0.0; + if (diffusion_te4 > 0.0) { + tmpTe2 = Grad2_par2new(Te); + mesh->communicate(tmpTe2); + tmpTe2.applyBoundary(); + ddt(Te) -= diffusion_te4 * Grad2_par2new(tmpTe2); + } + } - ddt(Vipar) -= Vipara * Grad_parP(P) / N0; - ddt(Vipar) += Vipara * bracket(Psi, P0, bm_mag) * B0 / N0; + ////////////////////////////////////////////////////////////////////// + if (compress0) { // parallel velocity equation + TRACE("ddt(Vipar)"); - if (diamag) { - ddt(Vipar) -= bracket(B0 * phi0, Vipar, bm_exb); - } + ddt(Vipar) = 0.0; - if (nonlinear) { - ddt(Vipar) -= bracket(B0 * phi, Vipar, bm_exb); - } + ddt(Vipar) -= Vipara * Grad_parP(P) / N0; + ddt(Vipar) += Vipara * bracket(Psi, P0, bm_mag) * B0 / N0; - // parallel hyper-viscous diffusion for vector potential - if (diffusion_v4 > 0.0) { - tmpVp2 = Grad2_par2new(Vipar); - mesh->communicate(tmpVp2); - tmpVp2.applyBoundary(); - ddt(Vipar) -= diffusion_v4 * Grad2_par2new(tmpVp2); - } + if (diamag) { + ddt(Vipar) -= bracket(B0 * phi0, Vipar, bm_exb); + } - if (sink_vp > 0.0) { - Field2D V0tmp = 0.; - ddt(Vipar) -= sink_vp * sink_tanhxl(V0tmp, Vipar, sp_width, sp_length); // sink - } - } + if (nonlinear) { + ddt(Vipar) -= bracket(B0 * phi, Vipar, bm_exb); + } - /////////////////////////////////////////////////////////////////////// + // parallel hyper-viscous diffusion for vector potential + if (diffusion_v4 > 0.0) { + tmpVp2 = Grad2_par2new(Vipar); + mesh->communicate(tmpVp2); + tmpVp2.applyBoundary(); + ddt(Vipar) -= diffusion_v4 * Grad2_par2new(tmpVp2); + } - if (filter_z) { - // Filter out all except filter_z_mode - TRACE("filter_z"); + if (sink_vp > 0.0) { + Field2D V0tmp = 0.; + ddt(Vipar) -= sink_vp * sink_tanhxl(V0tmp, Vipar, sp_width, sp_length); // sink + } + } - ddt(Psi) = filter(ddt(Psi), filter_z_mode); + /////////////////////////////////////////////////////////////////////// - ddt(U) = filter(ddt(U), filter_z_mode); + if (filter_z) { + // Filter out all except filter_z_mode + TRACE("filter_z"); - ddt(Ni) = filter(ddt(Ni), filter_z_mode); + ddt(Psi) = filter(ddt(Psi), filter_z_mode); - ddt(Ti) = filter(ddt(Ti), filter_z_mode); + ddt(U) = filter(ddt(U), filter_z_mode); - ddt(Te) = filter(ddt(Te), filter_z_mode); + ddt(Ni) = filter(ddt(Ni), filter_z_mode); - if (compress0) { - ddt(Vipar) = filter(ddt(Vipar), filter_z_mode); - } - } + ddt(Ti) = filter(ddt(Ti), filter_z_mode); - /////////////////////////////////////////////////////////////////////// + ddt(Te) = filter(ddt(Te), filter_z_mode); - if (low_pass_z > 0) { - // Low-pass filter, keeping n up to low_pass_z - TRACE("low_pass_z"); + if (compress0) { + ddt(Vipar) = filter(ddt(Vipar), filter_z_mode); + } + } - if (!emass) { - ddt(Psi) = lowPass(ddt(Psi), low_pass_z, zonal_field); - } else { - ddt(Ajpar) = lowPass(ddt(Ajpar), low_pass_z, zonal_field); - } + /////////////////////////////////////////////////////////////////////// - ddt(U) = lowPass(ddt(U), low_pass_z, zonal_flow); + if (low_pass_z > 0) { + // Low-pass filter, keeping n up to low_pass_z + TRACE("low_pass_z"); - ddt(Ti) = lowPass(ddt(Ti), low_pass_z, zonal_bkgd); - ddt(Te) = lowPass(ddt(Te), low_pass_z, zonal_bkgd); - ddt(Ni) = lowPass(ddt(Ni), low_pass_z, zonal_bkgd); + if (!emass) { + ddt(Psi) = lowPass(ddt(Psi), low_pass_z, zonal_field); + } else { + ddt(Ajpar) = lowPass(ddt(Ajpar), low_pass_z, zonal_field); + } - if (compress0) { - ddt(Vipar) = lowPass(ddt(Vipar), low_pass_z, zonal_bkgd); - } - } + ddt(U) = lowPass(ddt(U), low_pass_z, zonal_flow); - if (damp_width > 0) { - for (int i = 0; i < damp_width; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - if (mesh->firstX()) { - ddt(U)(i, j, k) -= U(i, j, k) / damp_t_const; + ddt(Ti) = lowPass(ddt(Ti), low_pass_z, zonal_bkgd); + ddt(Te) = lowPass(ddt(Te), low_pass_z, zonal_bkgd); + ddt(Ni) = lowPass(ddt(Ni), low_pass_z, zonal_bkgd); + + if (compress0) { + ddt(Vipar) = lowPass(ddt(Vipar), low_pass_z, zonal_bkgd); } - if (mesh->lastX()) { - ddt(U)(mesh->LocalNx - 1 - i, j, k) -= - U(mesh->LocalNx - 1 - i, j, k) / damp_t_const; + } + + if (damp_width > 0) { + for (int i = 0; i < damp_width; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + if (mesh->firstX()) { + ddt(U)(i, j, k) -= U(i, j, k) / damp_t_const; + } + if (mesh->lastX()) { + ddt(U)(mesh->LocalNx - 1 - i, j, k) -= + U(mesh->LocalNx - 1 - i, j, k) / damp_t_const; + } + } + } } - } } - } - } - if (filter_nl > 0) { - TRACE("filter_nl"); - ddt(Ni) = nl_filter(ddt(Ni), filter_nl); - } + if (filter_nl > 0) { + TRACE("filter_nl"); + ddt(Ni) = nl_filter(ddt(Ni), filter_nl); + } - first_run = false; + first_run = false; - return 0; - } + return 0; + } }; BOUTMAIN(Elm_6f) diff --git a/examples/conducting-wall-mode/cwm.cxx b/examples/conducting-wall-mode/cwm.cxx index 6442e37857..6a5d7e1252 100644 --- a/examples/conducting-wall-mode/cwm.cxx +++ b/examples/conducting-wall-mode/cwm.cxx @@ -5,6 +5,7 @@ * Model version in the code created by M. Umansky and J. Myra. *******************************************************************************/ #include +#include #include #include @@ -28,8 +29,7 @@ class CWM : public PhysicsModel { // Phi boundary conditions Field3D dphi_bc_ydown, dphi_bc_yup; - // Metric coefficients - Field2D Rxy, Bpxy, Btxy, hthe, Zxy; + Field2D Zxy; // parameters BoutReal Te_x, Ni_x, Vi_x, bmag, rho_s, fmei, AA, ZZ; @@ -54,7 +54,6 @@ class CWM : public PhysicsModel { std::unique_ptr phiSolver{nullptr}; int init(bool UNUSED(restarting)) override { - Field2D I; // Shear factor /************* LOAD DATA FROM GRID FILE ****************/ @@ -63,10 +62,6 @@ class CWM : public PhysicsModel { coord = mesh->getCoordinates(); - // Load metrics - GRID_LOAD(Rxy, Zxy, Bpxy, Btxy, hthe); - coord->setDx(mesh->get("dpsi")); - mesh->get(I, "sinty"); // Load normalisation values GRID_LOAD(Te_x, Ni_x, bmag); @@ -124,6 +119,8 @@ class CWM : public PhysicsModel { hthe0 / rho_s); } + auto tokamak_options = bout::TokamakOptions(*mesh); + /************** NORMALISE QUANTITIES *****************/ output.write("\tNormalising to rho_s = {:e}\n", rho_s); @@ -133,46 +130,21 @@ class CWM : public PhysicsModel { Te0 /= Te_x; // Normalise geometry - Rxy /= rho_s; - hthe /= rho_s; - I *= rho_s * rho_s * (bmag / 1e4) * ShearFactor; - coord->setDx(coord->dx() / (rho_s * rho_s * (bmag / 1e4))); - - // Normalise magnetic field - Bpxy /= (bmag / 1.e4); - Btxy /= (bmag / 1.e4); - coord->setBxy(coord->Bxy() / (bmag / 1.e4)); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, rho_s, bmag / 1e4, ShearFactor); // Set nu nu = nu_hat * Ni0 / pow(Te0, 1.5); - /**************** CALCULATE METRICS ******************/ - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(I) * g11 + SQ(coord->Bxy()) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + SQ(I * Rxy); - const auto g_22 = SQ(coord->Bxy() * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; - - coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coord->setJ(hthe / Bpxy); - /**************** SET EVOLVING VARIABLES *************/ // Tell BOUT++ which variables to evolve // add evolving variables to the communication object SOLVE_FOR(rho, te); + Field2D Rxy = tokamak_options.Rxy; + Field2D Bpxy = tokamak_options.Bpxy; + Field2D Btxy = tokamak_options.Btxy; + Field2D hthe = tokamak_options.hthe; SAVE_ONCE(Rxy, Bpxy, Btxy, Zxy, hthe); SAVE_ONCE(nu_hat, hthe0); diff --git a/examples/constraints/alfven-wave/alfven.cxx b/examples/constraints/alfven-wave/alfven.cxx index 2c12c9a568..5fc9a08475 100644 --- a/examples/constraints/alfven-wave/alfven.cxx +++ b/examples/constraints/alfven-wave/alfven.cxx @@ -2,7 +2,7 @@ #include #include #include -#include +#include /// Fundamental constants const BoutReal PI = 3.14159265; @@ -159,66 +159,22 @@ class Alfven : public PhysicsModel { } void LoadMetric(BoutReal Lnorm, BoutReal Bnorm) { - // Load metric coefficients from the mesh - Field2D Rxy, Bpxy, Btxy, hthe, sinty; - GRID_LOAD5(Rxy, Bpxy, Btxy, hthe, sinty); // Load metrics // Get the coordinates object Coordinates* coord = mesh->getCoordinates(); - // Checking for dpsi and qinty used in BOUT grids - Field2D dx; - if (!mesh->get(dx, "dpsi")) { - output << "\tUsing dpsi as the x grid spacing\n"; - coord->setDx(dx); // Only use dpsi if found - } else { - // dx will have been read already from the grid - output << "\tUsing dx as the x grid spacing\n"; - } - - Rxy /= Lnorm; - hthe /= Lnorm; - sinty *= SQ(Lnorm) * Bnorm; - coord->setDx(coord->dx() / (SQ(Lnorm) * Bnorm)); - - Bpxy /= Bnorm; - Btxy /= Bnorm; - coord->setBxy(coord->Bxy() / Bnorm); - // Check type of parallel transform std::string ptstr = Options::root()["mesh"]["paralleltransform"]["type"].withDefault("identity"); + BoutReal shearFactor = 1.0; if (lowercase(ptstr) == "shifted") { // Using shifted metric method - sinty = 0.0; // I disappears from metric + shearFactor = 0.0; // I disappears from metric } - BoutReal sbp = 1.0; // Sign of Bp - if (min(Bpxy, true) < 0.0) { - sbp = -1.0; - } - - // Calculate metric components - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(sinty) * g11 + SQ(coord->Bxy()) / g11; - const auto g12 = 0.0; - const auto g13 = -sinty * g11; - const auto g23 = -sbp * Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + SQ(sinty * Rxy); - const auto g_22 = SQ(coord->Bxy() * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = sbp * Btxy * hthe * sinty * Rxy / Bpxy; - const auto g_13 = sinty * Rxy * Rxy; - const auto g_23 = sbp * Btxy * hthe * Rxy / Bpxy; - - coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coord->setJ(hthe / Bpxy); + auto tokamak_options = bout::TokamakOptions(*mesh); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lnorm, Bnorm, shearFactor); } }; diff --git a/examples/dalf3/dalf3.cxx b/examples/dalf3/dalf3.cxx index 7f73ed56d7..96ee6cd10e 100644 --- a/examples/dalf3/dalf3.cxx +++ b/examples/dalf3/dalf3.cxx @@ -19,6 +19,7 @@ ****************************************************************/ #include +#include #include #include @@ -80,6 +81,8 @@ class DALF3 : public PhysicsModel { std::unique_ptr laplacexy{nullptr}; // Laplacian solver in X-Y (n=0) Field2D phi2D; // Axisymmetric potential, used when split_n0=true + bout::TokamakOptions tokamak_options = bout::TokamakOptions(*mesh); + protected: int init(bool UNUSED(restarting)) override { @@ -99,23 +102,6 @@ class DALF3 : public PhysicsModel { b0xcv.covariant = false; // Read contravariant components mesh->get(b0xcv, "bxcv"); // mixed units x: T y: m^-2 z: m^-2 - // Metric coefficients - Field2D Rxy, Bpxy, Btxy, hthe; - Field2D I; // Shear factor - - if (mesh->get(Rxy, "Rxy")) { // m - output_error.write("Error: Cannot read Rxy from grid\n"); - return 1; - } - if (mesh->get(Bpxy, "Bpxy")) { // T - output_error.write("Error: Cannot read Bpxy from grid\n"); - return 1; - } - mesh->get(Btxy, "Btxy"); // T - mesh->get(B0, "Bxy"); // T - mesh->get(hthe, "hthe"); // m - mesh->get(I, "sinty"); // m^-2 T^-1 - ////////////////////////////////////////////////////////////// // Options @@ -174,10 +160,11 @@ class DALF3 : public PhysicsModel { std::string ptstr = Options::root()["mesh"]["paralleltransform"]["type"].withDefault("identity"); + BoutReal shearFactor = 1.0; if (lowercase(ptstr) == "shifted") { // Dimits style, using local coordinate system - b0xcv.z += I * b0xcv.x; - I = 0.0; // I disappears from metric + b0xcv.z += tokamak_options.I * b0xcv.x; + shearFactor = 0.0; // I disappears from metric } /////////////////////////////////////////////////// @@ -236,37 +223,7 @@ class DALF3 : public PhysicsModel { b0xcv.z *= rho_s * rho_s; // Metrics - Rxy /= rho_s; - hthe /= rho_s; - I *= rho_s * rho_s * Bnorm; - Bpxy /= Bnorm; - Btxy /= Bnorm; - B0 /= Bnorm; - - coord->setDx(coord->dx() / (rho_s * rho_s * Bnorm)); - - /////////////////////////////////////////////////// - // CALCULATE METRICS - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(I) * g11 + SQ(B0) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + SQ(I * Rxy); - const auto g_22 = SQ(B0 * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; - - coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coord->setJ(hthe / Bpxy); - coord->setBxy(B0); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, rho_s, Bnorm, shearFactor); SOLVE_FOR3(Vort, Pe, Vpar); comms.add(Vort, Pe, Vpar); diff --git a/examples/elm-pb-outerloop/elm_pb_outerloop.cxx b/examples/elm-pb-outerloop/elm_pb_outerloop.cxx index 747d1f8f6e..1300cb6421 100644 --- a/examples/elm-pb-outerloop/elm_pb_outerloop.cxx +++ b/examples/elm-pb-outerloop/elm_pb_outerloop.cxx @@ -28,7 +28,6 @@ /*******************************************************************************/ -#include #include #include #include @@ -47,6 +46,7 @@ #include #include #include +#include #include // Defines BOUT_FOR_RAJA @@ -94,2021 +94,1991 @@ BOUT_OVERRIDE_DEFAULT_OPTION("phi:bndry_xout", "none"); /// 3-field ELM simulation class ELMpb : public PhysicsModel { private: - // 2D inital profiles - Field2D J0, P0; // Current and pressure - Vector2D b0xcv; // Curvature term - Field2D beta; // Used for Vpar terms - Coordinates::FieldMetric gradparB; - Field2D phi0; // When diamagnetic terms used - Field2D Psixy, x; - Coordinates::FieldMetric U0; // 0th vorticity of equilibrium flow, - // radial flux coordinate, normalized radial flux coordinate - - bool constn0; - // the total height, average width and center of profile of N0 - BoutReal n0_height, n0_ave, n0_width, n0_center, n0_bottom_x, Nbar, Tibar, Tebar; - - BoutReal Tconst; // the ampitude of constant temperature - - Field2D N0, Ti0, Te0, Ne0; // number density and temperature - Field2D Pi0, Pe0; - Field2D q95; - Field3D ubyn; - bool n0_fake_prof, T0_fake_prof; - - // B field vectors - Vector2D B0vec; // B0 field vector - - // V0 field vectors - Vector2D V0net; // net flow - - // 3D evolving variables - Field3D U, Psi, P, Vpar; - - // Derived 3D variables - Field3D Jpar, phi; // Parallel current, electric potential - - Field3D Jpar2; // Delp2 of Parallel current - - Field3D tmpP2; // Grad2_par2new of pressure - Field3D tmpU2; // Grad2_par2new of Parallel vorticity - Field3D tmpA2; // Grad2_par2new of Parallel vector potential - - // Constraint - Field3D C_phi; - - // Parameters - BoutReal density; // Number density [m^-3] - BoutReal Bbar, Lbar, Tbar, Va; // Normalisation constants - BoutReal dnorm; // For diamagnetic terms: 1 / (2. * wci * Tbar) - BoutReal dia_fact; // Multiply diamagnetic term by this - BoutReal delta_i; // Normalized ion skin depth - BoutReal omega_i; // ion gyrofrequency - - BoutReal diffusion_p4; // parallel hyper-viscous diffusion for pressure - BoutReal diffusion_u4; // parallel hyper-viscous diffusion for vorticity - BoutReal diffusion_a4; // parallel hyper-viscous diffusion for vector potential - - BoutReal diffusion_par; // Parallel pressure diffusion - BoutReal heating_P; // heating power in pressure - BoutReal hp_width; // heating profile radial width in pressure - BoutReal hp_length; // heating radial domain in pressure - BoutReal sink_P; // sink in pressure - BoutReal sp_width; // sink profile radial width in pressure - BoutReal sp_length; // sink radial domain in pressure - - BoutReal sink_Ul; // left edge sink in vorticity - BoutReal su_widthl; // left edge sink profile radial width in vorticity - BoutReal su_lengthl; // left edge sink radial domain in vorticity - - BoutReal sink_Ur; // right edge sink in vorticity - BoutReal su_widthr; // right edge sink profile radial width in vorticity - BoutReal su_lengthr; // right edge sink radial domain in vorticity - - BoutReal viscos_par; // Parallel viscosity - BoutReal viscos_perp; // Perpendicular viscosity - BoutReal hyperviscos; // Hyper-viscosity (radial) - Field3D hyper_mu_x; // Hyper-viscosity coefficient - - Field3D Dperp2Phi0, Dperp2Phi, GradPhi02, - GradPhi2; // Temporary variables for gyroviscous - Field3D GradparPhi02, GradparPhi2, GradcPhi, GradcparPhi; - Field3D Dperp2Pi0, Dperp2Pi, bracketPhi0P, bracketPhiP0, bracketPhiP; - BoutReal Upara2; - - // options - bool include_curvature, include_jpar0, compress; - bool evolve_pressure, gyroviscous; - - BoutReal vacuum_pressure; - BoutReal vacuum_trans; // Transition width - Field3D vac_mask; - - bool nonlinear; - bool evolve_jpar; - BoutReal g; // Only if compressible - bool phi_curv; - - // Poisson brackets: b0 x Grad(f) dot Grad(g) / B = [f, g] - // Method to use: BRACKET_ARAKAWA, BRACKET_STD or BRACKET_SIMPLE - /* - * Bracket method - * - * BRACKET_STD - Same as b0xGrad_dot_Grad, methods in BOUT.inp - * BRACKET_SIMPLE - Subset of terms, used in BOUT-06 - * BRACKET_ARAKAWA - Arakawa central differencing (2nd order) - * BRACKET_CTU - 1st order upwind method - * - */ - - // Bracket method for advection terms - BRACKET_METHOD bm_exb; - BRACKET_METHOD bm_mag; - int bm_exb_flag; - int bm_mag_flag; - /* BRACKET_METHOD bm_ExB = BRACKET_STD; - BRACKET_METHOD bm_mflutter = BRACKET_STD; */ - - bool diamag; - bool diamag_grad_t; // Grad_par(Te) term in Psi equation - bool diamag_phi0; // Include the diamagnetic equilibrium phi0 - - bool eHall; - BoutReal AA; // ion mass in units of the proton mass; AA=Mi/Mp - - // net flow, Er=-R*Bp*Dphi0,Dphi0=-D_min-0.5*D_0*(1.0-tanh(D_s*(x-x0))) - Field2D V0; // net flow amplitude - Field2D Dphi0; // differential potential to flux - BoutReal D_0; // potential amplitude - BoutReal D_s; // shear parameter - BoutReal x0; // velocity peak location - BoutReal sign; // direction of flow - BoutReal Psiaxis, Psibndry; - bool withflow; - bool K_H_term; // Kelvin-Holmhotz term - Field2D perp; // for test - BoutReal D_min; // constant in flow - - // for C_mod - bool experiment_Er; // read in total Er from experiment - - bool nogradparj; - bool filter_z; - int filter_z_mode; - int low_pass_z; - bool zonal_flow; - bool zonal_field; - bool zonal_bkgd; - - bool split_n0; // Solve the n=0 component of potential + // 2D inital profiles + Field2D J0, P0; // Current and pressure + Vector2D b0xcv; // Curvature term + Field2D beta; // Used for Vpar terms + Coordinates::FieldMetric gradparB; + Field2D phi0; // When diamagnetic terms used + Field2D Psixy, x; + Coordinates::FieldMetric U0; // 0th vorticity of equilibrium flow, + // radial flux coordinate, normalized radial flux coordinate + + bool constn0; + // the total height, average width and center of profile of N0 + BoutReal n0_height, n0_ave, n0_width, n0_center, n0_bottom_x, Nbar, Tibar, Tebar; + + BoutReal Tconst; // the ampitude of constant temperature + + Field2D N0, Ti0, Te0, Ne0; // number density and temperature + Field2D Pi0, Pe0; + Field2D q95; + Field3D ubyn; + bool n0_fake_prof, T0_fake_prof; + + // B field vectors + Vector2D B0vec; // B0 field vector + + // V0 field vectors + Vector2D V0net; // net flow + + // 3D evolving variables + Field3D U, Psi, P, Vpar; + + // Derived 3D variables + Field3D Jpar, phi; // Parallel current, electric potential + + Field3D Jpar2; // Delp2 of Parallel current + + Field3D tmpP2; // Grad2_par2new of pressure + Field3D tmpU2; // Grad2_par2new of Parallel vorticity + Field3D tmpA2; // Grad2_par2new of Parallel vector potential + + // Constraint + Field3D C_phi; + + // Parameters + BoutReal density; // Number density [m^-3] + BoutReal Bbar, Lbar, Tbar, Va; // Normalisation constants + BoutReal dnorm; // For diamagnetic terms: 1 / (2. * wci * Tbar) + BoutReal dia_fact; // Multiply diamagnetic term by this + BoutReal delta_i; // Normalized ion skin depth + BoutReal omega_i; // ion gyrofrequency + + BoutReal diffusion_p4; // parallel hyper-viscous diffusion for pressure + BoutReal diffusion_u4; // parallel hyper-viscous diffusion for vorticity + BoutReal diffusion_a4; // parallel hyper-viscous diffusion for vector potential + + BoutReal diffusion_par; // Parallel pressure diffusion + BoutReal heating_P; // heating power in pressure + BoutReal hp_width; // heating profile radial width in pressure + BoutReal hp_length; // heating radial domain in pressure + BoutReal sink_P; // sink in pressure + BoutReal sp_width; // sink profile radial width in pressure + BoutReal sp_length; // sink radial domain in pressure + + BoutReal sink_Ul; // left edge sink in vorticity + BoutReal su_widthl; // left edge sink profile radial width in vorticity + BoutReal su_lengthl; // left edge sink radial domain in vorticity + + BoutReal sink_Ur; // right edge sink in vorticity + BoutReal su_widthr; // right edge sink profile radial width in vorticity + BoutReal su_lengthr; // right edge sink radial domain in vorticity + + BoutReal viscos_par; // Parallel viscosity + BoutReal viscos_perp; // Perpendicular viscosity + BoutReal hyperviscos; // Hyper-viscosity (radial) + Field3D hyper_mu_x; // Hyper-viscosity coefficient + + Field3D Dperp2Phi0, Dperp2Phi, GradPhi02, + GradPhi2; // Temporary variables for gyroviscous + Field3D GradparPhi02, GradparPhi2, GradcPhi, GradcparPhi; + Field3D Dperp2Pi0, Dperp2Pi, bracketPhi0P, bracketPhiP0, bracketPhiP; + BoutReal Upara2; + + // options + bool include_curvature, include_jpar0, compress; + bool evolve_pressure, gyroviscous; + + BoutReal vacuum_pressure; + BoutReal vacuum_trans; // Transition width + Field3D vac_mask; + + bool nonlinear; + bool evolve_jpar; + BoutReal g; // Only if compressible + bool phi_curv; + + // Poisson brackets: b0 x Grad(f) dot Grad(g) / B = [f, g] + // Method to use: BRACKET_ARAKAWA, BRACKET_STD or BRACKET_SIMPLE + /* + * Bracket method + * + * BRACKET_STD - Same as b0xGrad_dot_Grad, methods in BOUT.inp + * BRACKET_SIMPLE - Subset of terms, used in BOUT-06 + * BRACKET_ARAKAWA - Arakawa central differencing (2nd order) + * BRACKET_CTU - 1st order upwind method + * + */ + + // Bracket method for advection terms + BRACKET_METHOD bm_exb; + BRACKET_METHOD bm_mag; + int bm_exb_flag; + int bm_mag_flag; + /* BRACKET_METHOD bm_ExB = BRACKET_STD; + BRACKET_METHOD bm_mflutter = BRACKET_STD; */ + + bool diamag; + bool diamag_grad_t; // Grad_par(Te) term in Psi equation + bool diamag_phi0; // Include the diamagnetic equilibrium phi0 + + bool eHall; + BoutReal AA; // ion mass in units of the proton mass; AA=Mi/Mp + + // net flow, Er=-R*Bp*Dphi0,Dphi0=-D_min-0.5*D_0*(1.0-tanh(D_s*(x-x0))) + Field2D V0; // net flow amplitude + Field2D Dphi0; // differential potential to flux + BoutReal D_0; // potential amplitude + BoutReal D_s; // shear parameter + BoutReal x0; // velocity peak location + BoutReal sign; // direction of flow + BoutReal Psiaxis, Psibndry; + bool withflow; + bool K_H_term; // Kelvin-Holmhotz term + Field2D perp; // for test + BoutReal D_min; // constant in flow + + // for C_mod + bool experiment_Er; // read in total Er from experiment + + bool nogradparj; + bool filter_z; + int filter_z_mode; + int low_pass_z; + bool zonal_flow; + bool zonal_field; + bool zonal_bkgd; + + bool split_n0; // Solve the n=0 component of potential #if BOUT_HAS_HYPRE - std::unique_ptr laplacexy{nullptr}; // Laplacian solver in X-Y (n=0) + std::unique_ptr laplacexy{nullptr}; // Laplacian solver in X-Y (n=0) #else - std::unique_ptr laplacexy{nullptr}; // Laplacian solver in X-Y (n=0) + std::unique_ptr laplacexy{nullptr}; // Laplacian solver in X-Y (n=0) #endif - Field2D phi2D; // Axisymmetric phi - - bool relax_j_vac; - BoutReal relax_j_tconst; // Time-constant for j relax - - bool smooth_j_x; // Smooth Jpar in the x direction - - int jpar_bndry_width; // Zero jpar in a boundary region - - bool sheath_boundaries; // Apply sheath boundaries in Y - - bool parallel_lr_diff; // Use left and right shifted stencils for parallel differences - - bool phi_constraint; // Solver for phi using a solver constraint - - bool include_rmp; // Include RMP coil perturbation - bool simple_rmp; // Just use a simple form for the perturbation - - BoutReal rmp_factor; // Multiply amplitude by this factor - BoutReal rmp_ramp; // Ramp-up time for RMP [s]. negative -> instant - BoutReal rmp_freq; // Amplitude oscillation frequency [Hz] (negative -> no oscillation) - BoutReal rmp_rotate; // Rotation rate [Hz] - bool rmp_vac_mask; - Field3D rmp_Psi0; // Parallel vector potential from Resonant Magnetic Perturbation (RMP) - // coils - Field3D rmp_Psi; // Value used in calculations - Field3D rmp_dApdt; // Time variation - - BoutReal vac_lund, core_lund; // Lundquist number S = (Tau_R / Tau_A). -ve -> infty - BoutReal vac_resist, core_resist; // The resistivities (just 1 / S) - Field3D eta; // Resistivity profile (1 / S) - bool spitzer_resist; // Use Spitzer formula for resistivity - BoutReal Zeff; // Z effective for resistivity formula - - BoutReal hyperresist; // Hyper-resistivity coefficient (in core only) - BoutReal ehyperviscos; // electron Hyper-viscosity coefficient - - int damp_width; // Width of inner damped region - BoutReal damp_t_const; // Timescale of damping - - // Metric coefficients - Field2D Rxy, Bpxy, Btxy, B0, hthe; - Field2D I; // Shear factor - - const BoutReal MU0 = 4.0e-7 * PI; - const BoutReal Mi = 2.0 * 1.6726e-27; // Ion mass - const BoutReal Me = 9.1094e-31; // Electron mass - const BoutReal mi_me = Mi / Me; - - // Communication objects - FieldGroup comms; - - /// Solver for inverting Laplacian - std::unique_ptr phiSolver{nullptr}; - std::unique_ptr aparSolver{nullptr}; - - const Field2D N0tanh(BoutReal n0_height, BoutReal n0_ave, BoutReal n0_width, - BoutReal n0_center, BoutReal n0_bottom_x) { - Field2D result; - result.allocate(); - - BoutReal Grid_NX, Grid_NXlimit; // the grid number on x, and the - BoutReal Jysep; - mesh->get(Grid_NX, "nx"); - mesh->get(Jysep, "jyseps1_1"); - Grid_NXlimit = n0_bottom_x * Grid_NX; - output.write("Jysep1_1 = {:d} Grid number = {:e}\n", int(Jysep), Grid_NX); - - if (Jysep > 0.) { // for single null geometry - BoutReal Jxsep, Jysep2; - mesh->get(Jxsep, "ixseps1"); - mesh->get(Jysep2, "jyseps2_2"); - - for (auto i : result) { - BoutReal mgx = mesh->GlobalX(i.x()); - BoutReal xgrid_num = (Jxsep + 1.) / Grid_NX; - - int globaly = mesh->getGlobalYIndex(i.y()); - - if (mgx > xgrid_num || (globaly <= int(Jysep) - 2) - || (globaly > int(Jysep2) + 2)) { - mgx = xgrid_num; - } - BoutReal rlx = mgx - n0_center; - BoutReal temp = exp(rlx / n0_width); - BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); - result[i] = 0.5 * (1.0 - dampr) * n0_height + n0_ave; - } - } else { // circular geometry - for (auto i : result) { - BoutReal mgx = mesh->GlobalX(i.x()); - BoutReal xgrid_num = Grid_NXlimit / Grid_NX; - if (mgx > xgrid_num) { - mgx = xgrid_num; - } - BoutReal rlx = mgx - n0_center; - BoutReal temp = exp(rlx / n0_width); - BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); - result[i] = 0.5 * (1.0 - dampr) * n0_height + n0_ave; - } - } + Field2D phi2D; // Axisymmetric phi - mesh->communicate(result); + bool relax_j_vac; + BoutReal relax_j_tconst; // Time-constant for j relax - return result; - } + bool smooth_j_x; // Smooth Jpar in the x direction -public: - // Note: The rhs() function needs to be public so that RAJA can use CUDA + int jpar_bndry_width; // Zero jpar in a boundary region - int init(bool restarting) override { - Coordinates* metric = mesh->getCoordinates(); + bool sheath_boundaries; // Apply sheath boundaries in Y - output.write("Solving high-beta flute reduced equations\n"); - output.write("\tFile : {:s}\n", __FILE__); - output.write("\tCompiled: {:s} at {:s}\n", __DATE__, __TIME__); + bool parallel_lr_diff; // Use left and right shifted stencils for parallel differences - ////////////////////////////////////////////////////////////// - // Load data from the grid + bool phi_constraint; // Solver for phi using a solver constraint - // Load 2D profiles - mesh->get(J0, "Jpar0"); // A / m^2 - mesh->get(P0, "pressure"); // Pascals + bool include_rmp; // Include RMP coil perturbation + bool simple_rmp; // Just use a simple form for the perturbation - // Load curvature term - b0xcv.covariant = false; // Read contravariant components - mesh->get(b0xcv, "bxcv"); // mixed units x: T y: m^-2 z: m^-2 + BoutReal rmp_factor; // Multiply amplitude by this factor + BoutReal rmp_ramp; // Ramp-up time for RMP [s]. negative -> instant + BoutReal rmp_freq; // Amplitude oscillation frequency [Hz] (negative -> no oscillation) + BoutReal rmp_rotate; // Rotation rate [Hz] + bool rmp_vac_mask; + Field3D rmp_Psi0; // Parallel vector potential from Resonant Magnetic Perturbation (RMP) + // coils + Field3D rmp_Psi; // Value used in calculations + Field3D rmp_dApdt; // Time variation - // Load metrics - if (mesh->get(Rxy, "Rxy") != 0) { // m - throw BoutException("Error: Cannot read Rxy from grid\n"); - } - if (mesh->get(Bpxy, "Bpxy") != 0) { // T - throw BoutException("Error: Cannot read Bpxy from grid\n"); - } - mesh->get(Btxy, "Btxy"); // T - mesh->get(B0, "Bxy"); // T - mesh->get(hthe, "hthe"); // m - mesh->get(I, "sinty"); // m^-2 T^-1 - mesh->get(Psixy, "psixy"); // get Psi - mesh->get(Psiaxis, "psi_axis"); // axis flux - mesh->get(Psibndry, "psi_bndry"); // edge flux - - // Set locations of staggered variables - // Note, use of staggered grids in elm-pb is untested and may not be completely - // implemented. Parallel boundary conditions are especially likely to be wrong. - if (mesh->StaggerGrids) { - loc = CELL_YLOW; - } else { - loc = CELL_CENTRE; - } - Jpar.setLocation(loc); - Vpar.setLocation(loc); - Psi.setLocation(loc); - eta.setLocation(loc); - - ////////////////////////////////////////////////////////////// - auto& globalOptions = Options::root(); - auto& options = globalOptions["highbeta"]; - - constn0 = options["constn0"].withDefault(true); - // use the hyperbolic profile of n0. If both n0_fake_prof and - // T0_fake_prof are false, use the profiles from grid file - n0_fake_prof = options["n0_fake_prof"].withDefault(false); - // the total height of profile of N0, in percentage of Ni_x - n0_height = options["n0_height"].withDefault(0.4); - // the center or average of N0, in percentage of Ni_x - n0_ave = options["n0_ave"].withDefault(0.01); - // the width of the gradient of N0,in percentage of x - n0_width = options["n0_width"].withDefault(0.1); - // the grid number of the center of N0, in percentage of x - n0_center = options["n0_center"].withDefault(0.633); - // the start of flat region of N0 on SOL side, in percentage of x - n0_bottom_x = options["n0_bottom_x"].withDefault(0.81); - T0_fake_prof = options["T0_fake_prof"].withDefault(false); - // the amplitude of constant temperature, in percentage - Tconst = options["Tconst"].withDefault(-1.0); - - density = options["density"].doc("Number density [m^-3]").withDefault(1.0e19); - - evolve_jpar = - options["evolve_jpar"].doc("If true, evolve J raher than Psi").withDefault(false); - phi_constraint = options["phi_constraint"] - .doc("Use solver constraint for phi?") - .withDefault(false); - - // Effects to include/exclude - include_curvature = options["include_curvature"].withDefault(true); - include_jpar0 = options["include_jpar0"].withDefault(true); - evolve_pressure = options["evolve_pressure"].withDefault(true); - nogradparj = options["nogradparj"].withDefault(false); - - compress = options["compress"] - .doc("Include compressibility effects (evolve Vpar)?") - .withDefault(false); - gyroviscous = options["gyroviscous"].withDefault(false); - nonlinear = options["nonlinear"].doc("Include nonlinear terms?").withDefault(false); - - // option for ExB Poisson Bracket - bm_exb_flag = options["bm_exb_flag"] - .doc("ExB Poisson bracket method. 0=standard;1=simple;2=arakawa") - .withDefault(0); - switch (bm_exb_flag) { - case 0: { - bm_exb = BRACKET_STD; - output << "\tBrackets for ExB: default differencing\n"; - break; - } - case 1: { - bm_exb = BRACKET_SIMPLE; - output << "\tBrackets for ExB: simplified operator\n"; - break; - } - case 2: { - bm_exb = BRACKET_ARAKAWA; - output << "\tBrackets for ExB: Arakawa scheme\n"; - break; - } - case 3: { - bm_exb = BRACKET_CTU; - output << "\tBrackets for ExB: Corner Transport Upwind method\n"; - break; - } - default: - throw BoutException("Invalid choice of bracket method. Must be 0 - 3\n"); - } + BoutReal vac_lund, core_lund; // Lundquist number S = (Tau_R / Tau_A). -ve -> infty + BoutReal vac_resist, core_resist; // The resistivities (just 1 / S) + Field3D eta; // Resistivity profile (1 / S) + bool spitzer_resist; // Use Spitzer formula for resistivity + BoutReal Zeff; // Z effective for resistivity formula - bm_mag_flag = - options["bm_mag_flag"].doc("magnetic flutter Poisson Bracket").withDefault(0); - switch (bm_mag_flag) { - case 0: { - bm_mag = BRACKET_STD; - output << "\tBrackets: default differencing\n"; - break; - } - case 1: { - bm_mag = BRACKET_SIMPLE; - output << "\tBrackets: simplified operator\n"; - break; - } - case 2: { - bm_mag = BRACKET_ARAKAWA; - output << "\tBrackets: Arakawa scheme\n"; - break; - } - case 3: { - bm_mag = BRACKET_CTU; - output << "\tBrackets: Corner Transport Upwind method\n"; - break; - } - default: - throw BoutException("Invalid choice of bracket method. Must be 0 - 3\n"); - } + BoutReal hyperresist; // Hyper-resistivity coefficient (in core only) + BoutReal ehyperviscos; // electron Hyper-viscosity coefficient - eHall = options["eHall"] - .doc("electron Hall or electron parallel pressue gradient effects?") - .withDefault(false); - AA = options["AA"].doc("ion mass in units of proton mass").withDefault(1.0); - - diamag = options["diamag"].doc("Diamagnetic effects?").withDefault(false); - diamag_grad_t = options["diamag_grad_t"] - .doc("Grad_par(Te) term in Psi equation") - .withDefault(diamag); - diamag_phi0 = - options["diamag_phi0"].doc("Include equilibrium phi0").withDefault(diamag); - dia_fact = options["dia_fact"] - .doc("Scale diamagnetic effects by this factor") - .withDefault(1.0); - - // withflow or not - withflow = options["withflow"].withDefault(false); - // keep K-H term - K_H_term = options["K_H_term"].withDefault(true); - // velocity magnitude - D_0 = options["D_0"].withDefault(0.0); - // flowshear - D_s = options["D_s"].withDefault(0.0); - // flow location - x0 = options["x0"].withDefault(0.0); - // flow direction, -1 means negative electric field - sign = options["sign"].withDefault(1.0); - // a constant - D_min = options["D_min"].withDefault(3000.0); - - experiment_Er = options["experiment_Er"].withDefault(false); - - bool noshear = options["noshear"].withDefault(false); - - relax_j_vac = options["relax_j_vac"] - .doc("Relax vacuum current to zero") - .withDefault(false); - relax_j_tconst = options["relax_j_tconst"] - .doc("Time constant for relaxation of vacuum current. Alfven " - "(normalised) units") - .withDefault(0.1); - - // Toroidal filtering - filter_z = options["filter_z"] - .doc("Filter a single toroidal mode number? The mode to keep is " - "filter_z_mode.") - .withDefault(false); - filter_z_mode = options["filter_z_mode"] - .doc("Single toroidal mode number to keep") - .withDefault(1); - low_pass_z = options["low_pass_z"].doc("Low-pass filter").withDefault(-1); - zonal_flow = options["zonal_flow"] - .doc("Keep zonal (n=0) component of potential?") - .withDefault(false); - zonal_field = options["zonal_field"] - .doc("Keep zonal (n=0) component of magnetic potential?") - .withDefault(false); - zonal_bkgd = options["zonal_bkgd"] - .doc("Evolve zonal (n=0) pressure profile?") - .withDefault(false); - - // n = 0 electrostatic potential solve - split_n0 = options["split_n0"] - .doc("Solve zonal (n=0) component of potential using LaplaceXY?") - .withDefault(false); - if (split_n0) { - // Create an XY solver for n=0 component -#if BOUT_HAS_HYPRE - laplacexy = bout::utils::make_unique(mesh); -#else - laplacexy = bout::utils::make_unique(mesh); -#endif - // Set coefficients for Boussinesq solve - laplacexy->setCoefs(1.0, 0.0); - phi2D = 0.0; // Starting guess - phi2D.setBoundary("phi"); - } + int damp_width; // Width of inner damped region + BoutReal damp_t_const; // Timescale of damping - // Radial smoothing - smooth_j_x = options["smooth_j_x"].doc("Smooth Jpar in x").withDefault(false); - - // Jpar boundary region - jpar_bndry_width = options["jpar_bndry_width"] - .doc("Number of cells near the boundary where jpar = 0") - .withDefault(-1); - - sheath_boundaries = options["sheath_boundaries"] - .doc("Apply sheath boundaries in Y?") - .withDefault(false); - - // Parallel differencing - parallel_lr_diff = - options["parallel_lr_diff"] - .doc("Use left and right shifted stencils for parallel differences?") - .withDefault(false); - - // RMP-related options - include_rmp = options["include_rmp"] - .doc("Read RMP field rmp_A from grid?") - .withDefault(false); - - simple_rmp = - options["simple_rmp"].doc("Include a simple RMP model?").withDefault(false); - rmp_factor = options["rmp_factor"].withDefault(1.0); - rmp_ramp = options["rmp_ramp"].withDefault(-1.0); - rmp_freq = options["rmp_freq"].withDefault(-1.0); - rmp_rotate = options["rmp_rotate"].withDefault(0.0); - - // Vacuum region control - vacuum_pressure = - options["vacuum_pressure"] - .doc("Fraction of peak pressure, below which is considered vacuum.") - .withDefault(0.02); - vacuum_trans = - options["vacuum_trans"] - .doc("Vacuum boundary transition width, as fraction of peak pressure.") - .withDefault(0.005); - - // Resistivity and hyper-resistivity options - vac_lund = - options["vac_lund"].doc("Lundquist number in vacuum region").withDefault(0.0); - core_lund = - options["core_lund"].doc("Lundquist number in core region").withDefault(0.0); - hyperresist = - options["hyperresist"].doc("Hyper-resistivity coefficient").withDefault(-1.0); - ehyperviscos = options["ehyperviscos"] - .doc("electron Hyper-viscosity coefficient") - .withDefault(-1.0); - spitzer_resist = options["spitzer_resist"] - .doc("Use Spitzer resistivity?") - .withDefault(false); - Zeff = options["Zeff"].withDefault(2.0); // Z effective - - // Inner boundary damping - damp_width = options["damp_width"] - .doc("Width of the radial damping regions, in grid cells") - .withDefault(0); - damp_t_const = - options["damp_t_const"] - .doc("Time constant for damping in radial regions. Normalised time units.") - .withDefault(0.1); - - // Viscosity and hyper-viscosity - viscos_par = options["viscos_par"].doc("Parallel viscosity").withDefault(-1.0); - viscos_perp = options["viscos_perp"].doc("Perpendicular viscosity").withDefault(-1.0); - hyperviscos = options["hyperviscos"].doc("Radial hyperviscosity").withDefault(-1.0); - - diffusion_par = - options["diffusion_par"].doc("Parallel pressure diffusion").withDefault(-1.0); - diffusion_p4 = options["diffusion_p4"] - .doc("parallel hyper-viscous diffusion for pressure") - .withDefault(-1.0); - diffusion_u4 = options["diffusion_u4"] - .doc("parallel hyper-viscous diffusion for vorticity") - .withDefault(-1.0); - diffusion_a4 = options["diffusion_a4"] - .doc("parallel hyper-viscous diffusion for vector potential") - .withDefault(-1.0); - - // heating factor in pressure - // heating power in pressure - heating_P = options["heating_P"].withDefault(-1.0); - // the percentage of radial grid points for heating profile radial - // width in pressure - hp_width = options["hp_width"].withDefault(0.1); - // the percentage of radial grid points for heating profile radial - // domain in pressure - hp_length = options["hp_length"].withDefault(0.04); - - // sink factor in pressure - // sink in pressure - sink_P = options["sink_P"].withDefault(-1.0); - // the percentage of radial grid points for sink profile radial - // width in pressure - sp_width = options["sp_width"].withDefault(0.05); - // the percentage of radial grid points for sink profile radial - // domain in pressure - sp_length = options["sp_length"].withDefault(0.04); - - // left edge sink factor in vorticity - // left edge sink in vorticity - sink_Ul = options["sink_Ul"].withDefault(-1.0); - // the percentage of left edge radial grid points for sink profile - // radial width in vorticity - su_widthl = options["su_widthl"].withDefault(0.06); - // the percentage of left edge radial grid points for sink profile - // radial domain in vorticity - su_lengthl = options["su_lengthl"].withDefault(0.15); - - // right edge sink factor in vorticity - // right edge sink in vorticity - sink_Ur = options["sink_Ur"].withDefault(-1.0); - // the percentage of right edge radial grid points for sink profile - // radial width in vorticity - su_widthr = options["su_widthr"].withDefault(0.06); - // the percentage of right edge radial grid points for sink profile - // radial domain in vorticity - su_lengthr = options["su_lengthr"].withDefault(0.15); - - // Compressional terms - phi_curv = - options["phi_curv"].doc("ExB compression in P equation?").withDefault(true); - g = options["gamma"].doc("Ratio of specific heats").withDefault(5.0 / 3.0); - - x = (Psixy - Psiaxis) / (Psibndry - Psiaxis); - - if (experiment_Er) { // get er from experiment - mesh->get(Dphi0, "Epsi"); - diamag_phi0 = false; - K_H_term = false; - } else { - Dphi0 = -D_min - 0.5 * D_0 * (1.0 - tanh(D_s * (x - x0))); - } + bout::TokamakOptions tokamak_options = bout::TokamakOptions(*mesh); - if (sign < 0) { // change flow direction - Dphi0 *= -1; - } + const BoutReal MU0 = 4.0e-7 * PI; + const BoutReal Mi = 2.0 * 1.6726e-27; // Ion mass + const BoutReal Me = 9.1094e-31; // Electron mass + const BoutReal mi_me = Mi / Me; - V0 = -Rxy * Bpxy * Dphi0 / B0; + // Communication objects + FieldGroup comms; - if (simple_rmp) { - include_rmp = true; - } + /// Solver for inverting Laplacian + std::unique_ptr phiSolver{nullptr}; + std::unique_ptr aparSolver{nullptr}; - // toroidal and poloidal mode numbers - const int rmp_n = - options["rmp_n"].doc("Simple RMP toroidal mode number").withDefault(3); - const int rmp_m = - options["rmp_m"].doc("Simple RMP poloidal mode number").withDefault(9); - const int rmp_polwid = options["rmp_polwid"] - .doc("Poloidal width (-ve -> full, fraction of 2pi)") - .withDefault(-1.0); - const int rmp_polpeak = options["rmp_polpeak"] - .doc("Peak poloidal location (fraction of 2pi)") - .withDefault(0.5); - rmp_vac_mask = - options["rmp_vac_mask"].doc("Should a vacuum mask be applied?").withDefault(true); - // Divide n by the size of the domain - const int zperiod = globalOptions["zperiod"].withDefault(1); - - if (include_rmp) { - // Including external field coils. - if (simple_rmp) { - // Use a fairly simple form for the perturbation - Field2D pol_angle; - if (mesh->get(pol_angle, "pol_angle") != 0) { - output_warn.write(" ***WARNING: need poloidal angle for simple RMP\n"); - include_rmp = false; - } else { - if ((rmp_n % zperiod) != 0) { - output_warn.write( - " ***WARNING: rmp_n ({:d}) not a multiple of zperiod ({:d})\n", rmp_n, - zperiod); - } - - output.write("\tMagnetic perturbation: n = {:d}, m = {:d}, magnitude {:e} Tm\n", - rmp_n, rmp_m, rmp_factor); - - rmp_Psi0 = 0.0; - if (mesh->lastX()) { - // Set the outer boundary - for (int jx = mesh->LocalNx - 4; jx < mesh->LocalNx; jx++) { - for (int jy = 0; jy < mesh->LocalNy; jy++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { + const Field2D N0tanh(BoutReal n0_height, BoutReal n0_ave, BoutReal n0_width, + BoutReal n0_center, BoutReal n0_bottom_x) { + Field2D result; + result.allocate(); + + BoutReal Grid_NX, Grid_NXlimit; // the grid number on x, and the + BoutReal Jysep; + mesh->get(Grid_NX, "nx"); + mesh->get(Jysep, "jyseps1_1"); + Grid_NXlimit = n0_bottom_x * Grid_NX; + output.write("Jysep1_1 = {:d} Grid number = {:e}\n", int(Jysep), Grid_NX); + + if (Jysep > 0.) { // for single null geometry + BoutReal Jxsep, Jysep2; + mesh->get(Jxsep, "ixseps1"); + mesh->get(Jysep2, "jyseps2_2"); + + for (auto i: result) { + BoutReal mgx = mesh->GlobalX(i.x()); + BoutReal xgrid_num = (Jxsep + 1.) / Grid_NX; - BoutReal angle = - rmp_m * pol_angle(jx, jy) - + rmp_n * ((BoutReal)jz) * mesh->getCoordinates()->dz(jx, jy, jz); - rmp_Psi0(jx, jy, jz) = - (((BoutReal)(jx - 4)) / ((BoutReal)(mesh->LocalNx - 5))) - * rmp_factor * cos(angle); - if (rmp_polwid > 0.0) { - // Multiply by a Gaussian in poloidal angle - BoutReal gx = - ((pol_angle(jx, jy) / (2. * PI)) - rmp_polpeak) / rmp_polwid; - rmp_Psi0(jx, jy, jz) *= exp(-gx * gx); - } + int globaly = mesh->getGlobalYIndex(i.y()); + + if (mgx > xgrid_num || (globaly <= int(Jysep) - 2) + || (globaly > int(Jysep2) + 2)) { + mgx = xgrid_num; } - } + BoutReal rlx = mgx - n0_center; + BoutReal temp = exp(rlx / n0_width); + BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); + result[i] = 0.5 * (1.0 - dampr) * n0_height + n0_ave; } - } - - // Now have a simple model for Psi due to coils at the outer boundary - // Need to calculate Psi inside the domain, enforcing j = 0 - - Jpar = 0.0; - auto psiLap = std::unique_ptr{Laplacian::create(nullptr, loc)}; - psiLap->setInnerBoundaryFlags(INVERT_AC_GRAD); // Zero gradient inner BC - psiLap->setOuterBoundaryFlags(INVERT_SET); // Set to rmp_Psi0 on outer boundary - rmp_Psi0 = psiLap->solve(Jpar, rmp_Psi0); - mesh->communicate(rmp_Psi0); - } - } else { - // Load perturbation from grid file. - include_rmp = mesh->get(rmp_Psi0, "rmp_A") == 0; // Only include if found - if (!include_rmp) { - output_warn.write("WARNING: Couldn't read 'rmp_A' from grid file\n"); - } - // Multiply by factor - rmp_Psi0 *= rmp_factor; - } - } + } else { // circular geometry + for (auto i: result) { + BoutReal mgx = mesh->GlobalX(i.x()); + BoutReal xgrid_num = Grid_NXlimit / Grid_NX; + if (mgx > xgrid_num) { + mgx = xgrid_num; + } + BoutReal rlx = mgx - n0_center; + BoutReal temp = exp(rlx / n0_width); + BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); + result[i] = 0.5 * (1.0 - dampr) * n0_height + n0_ave; + } + } - if (!include_curvature) { - b0xcv = 0.0; - } + mesh->communicate(result); - if (!include_jpar0) { - J0 = 0.0; + return result; } - if (noshear) { - if (include_curvature) { - b0xcv.z += I * b0xcv.x; - } - I = 0.0; - } +public: + // Note: The rhs() function needs to be public so that RAJA can use CUDA - ////////////////////////////////////////////////////////////// - // SHIFTED RADIAL COORDINATES + int init(bool restarting) override { + Coordinates *metric = mesh->getCoordinates(); - if (mesh->IncIntShear) { - // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - metric->setIntShiftTorsion(I); + output.write("Solving high-beta flute reduced equations\n"); + output.write("\tFile : {:s}\n", __FILE__); + output.write("\tCompiled: {:s} at {:s}\n", __DATE__, __TIME__); - } else { - // Dimits style, using local coordinate system - if (include_curvature) { - b0xcv.z += I * b0xcv.x; - } - I = 0.0; // I disappears from metric - } + ////////////////////////////////////////////////////////////// + // Load data from the grid - ////////////////////////////////////////////////////////////// - // NORMALISE QUANTITIES + // Load 2D profiles + mesh->get(J0, "Jpar0"); // A / m^2 + mesh->get(P0, "pressure"); // Pascals - if (mesh->get(Bbar, "bmag") != 0) { // Typical magnetic field - Bbar = 1.0; - } - if (mesh->get(Lbar, "rmag") != 0) { // Typical length scale - Lbar = 1.0; - } + // Load curvature term + b0xcv.covariant = false; // Read contravariant components + mesh->get(b0xcv, "bxcv"); // mixed units x: T y: m^-2 z: m^-2 - Va = sqrt(Bbar * Bbar / (MU0 * density * Mi)); + mesh->get(Psixy, "psixy"); // get Psi + mesh->get(Psiaxis, "psi_axis"); // axis flux + mesh->get(Psibndry, "psi_bndry"); // edge flux - Tbar = Lbar / Va; + // Set locations of staggered variables + // Note, use of staggered grids in elm-pb is untested and may not be completely + // implemented. Parallel boundary conditions are especially likely to be wrong. + if (mesh->StaggerGrids) { + loc = CELL_YLOW; + } else { + loc = CELL_CENTRE; + } + Jpar.setLocation(loc); + Vpar.setLocation(loc); + Psi.setLocation(loc); + eta.setLocation(loc); + + ////////////////////////////////////////////////////////////// + auto &globalOptions = Options::root(); + auto &options = globalOptions["highbeta"]; + + constn0 = options["constn0"].withDefault(true); + // use the hyperbolic profile of n0. If both n0_fake_prof and + // T0_fake_prof are false, use the profiles from grid file + n0_fake_prof = options["n0_fake_prof"].withDefault(false); + // the total height of profile of N0, in percentage of Ni_x + n0_height = options["n0_height"].withDefault(0.4); + // the center or average of N0, in percentage of Ni_x + n0_ave = options["n0_ave"].withDefault(0.01); + // the width of the gradient of N0,in percentage of x + n0_width = options["n0_width"].withDefault(0.1); + // the grid number of the center of N0, in percentage of x + n0_center = options["n0_center"].withDefault(0.633); + // the start of flat region of N0 on SOL side, in percentage of x + n0_bottom_x = options["n0_bottom_x"].withDefault(0.81); + T0_fake_prof = options["T0_fake_prof"].withDefault(false); + // the amplitude of constant temperature, in percentage + Tconst = options["Tconst"].withDefault(-1.0); + + density = options["density"].doc("Number density [m^-3]").withDefault(1.0e19); + + evolve_jpar = + options["evolve_jpar"].doc("If true, evolve J raher than Psi").withDefault(false); + phi_constraint = options["phi_constraint"] + .doc("Use solver constraint for phi?") + .withDefault(false); - dnorm = dia_fact * Mi / (2. * 1.602e-19 * Bbar * Tbar); + // Effects to include/exclude + include_curvature = options["include_curvature"].withDefault(true); + include_jpar0 = options["include_jpar0"].withDefault(true); + evolve_pressure = options["evolve_pressure"].withDefault(true); + nogradparj = options["nogradparj"].withDefault(false); - delta_i = AA * 60.67 * 5.31e5 / sqrt(density / 1e6) / (Lbar * 100.0); + compress = options["compress"] + .doc("Include compressibility effects (evolve Vpar)?") + .withDefault(false); + gyroviscous = options["gyroviscous"].withDefault(false); + nonlinear = options["nonlinear"].doc("Include nonlinear terms?").withDefault(false); + + // option for ExB Poisson Bracket + bm_exb_flag = options["bm_exb_flag"] + .doc("ExB Poisson bracket method. 0=standard;1=simple;2=arakawa") + .withDefault(0); + switch (bm_exb_flag) { + case 0: { + bm_exb = BRACKET_STD; + output << "\tBrackets for ExB: default differencing\n"; + break; + } + case 1: { + bm_exb = BRACKET_SIMPLE; + output << "\tBrackets for ExB: simplified operator\n"; + break; + } + case 2: { + bm_exb = BRACKET_ARAKAWA; + output << "\tBrackets for ExB: Arakawa scheme\n"; + break; + } + case 3: { + bm_exb = BRACKET_CTU; + output << "\tBrackets for ExB: Corner Transport Upwind method\n"; + break; + } + default: + throw BoutException("Invalid choice of bracket method. Must be 0 - 3\n"); + } - output.write("Normalisations: Bbar = {:e} T Lbar = {:e} m\n", Bbar, Lbar); - output.write(" Va = {:e} m/s Tbar = {:e} s\n", Va, Tbar); - output.write(" dnorm = {:e}\n", dnorm); - output.write(" Resistivity\n"); + bm_mag_flag = + options["bm_mag_flag"].doc("magnetic flutter Poisson Bracket").withDefault(0); + switch (bm_mag_flag) { + case 0: { + bm_mag = BRACKET_STD; + output << "\tBrackets: default differencing\n"; + break; + } + case 1: { + bm_mag = BRACKET_SIMPLE; + output << "\tBrackets: simplified operator\n"; + break; + } + case 2: { + bm_mag = BRACKET_ARAKAWA; + output << "\tBrackets: Arakawa scheme\n"; + break; + } + case 3: { + bm_mag = BRACKET_CTU; + output << "\tBrackets: Corner Transport Upwind method\n"; + break; + } + default: + throw BoutException("Invalid choice of bracket method. Must be 0 - 3\n"); + } - if (gyroviscous) { - omega_i = 9.58e7 * Zeff * Bbar; - Upara2 = 0.5 / (Tbar * omega_i); - output.write("Upara2 = {:e} Omega_i = {:e}\n", Upara2, omega_i); - } + eHall = options["eHall"] + .doc("electron Hall or electron parallel pressue gradient effects?") + .withDefault(false); + AA = options["AA"].doc("ion mass in units of proton mass").withDefault(1.0); + + diamag = options["diamag"].doc("Diamagnetic effects?").withDefault(false); + diamag_grad_t = options["diamag_grad_t"] + .doc("Grad_par(Te) term in Psi equation") + .withDefault(diamag); + diamag_phi0 = + options["diamag_phi0"].doc("Include equilibrium phi0").withDefault(diamag); + dia_fact = options["dia_fact"] + .doc("Scale diamagnetic effects by this factor") + .withDefault(1.0); + + // withflow or not + withflow = options["withflow"].withDefault(false); + // keep K-H term + K_H_term = options["K_H_term"].withDefault(true); + // velocity magnitude + D_0 = options["D_0"].withDefault(0.0); + // flowshear + D_s = options["D_s"].withDefault(0.0); + // flow location + x0 = options["x0"].withDefault(0.0); + // flow direction, -1 means negative electric field + sign = options["sign"].withDefault(1.0); + // a constant + D_min = options["D_min"].withDefault(3000.0); + + experiment_Er = options["experiment_Er"].withDefault(false); + + bool noshear = options["noshear"].withDefault(false); + + relax_j_vac = options["relax_j_vac"] + .doc("Relax vacuum current to zero") + .withDefault(false); + relax_j_tconst = options["relax_j_tconst"] + .doc("Time constant for relaxation of vacuum current. Alfven " + "(normalised) units") + .withDefault(0.1); + + // Toroidal filtering + filter_z = options["filter_z"] + .doc("Filter a single toroidal mode number? The mode to keep is " + "filter_z_mode.") + .withDefault(false); + filter_z_mode = options["filter_z_mode"] + .doc("Single toroidal mode number to keep") + .withDefault(1); + low_pass_z = options["low_pass_z"].doc("Low-pass filter").withDefault(-1); + zonal_flow = options["zonal_flow"] + .doc("Keep zonal (n=0) component of potential?") + .withDefault(false); + zonal_field = options["zonal_field"] + .doc("Keep zonal (n=0) component of magnetic potential?") + .withDefault(false); + zonal_bkgd = options["zonal_bkgd"] + .doc("Evolve zonal (n=0) pressure profile?") + .withDefault(false); - if (eHall) { - output.write(" delta_i = {:e} AA = {:e} \n", delta_i, AA); - } + // n = 0 electrostatic potential solve + split_n0 = options["split_n0"] + .doc("Solve zonal (n=0) component of potential using LaplaceXY?") + .withDefault(false); + if (split_n0) { + // Create an XY solver for n=0 component +#if BOUT_HAS_HYPRE + laplacexy = bout::utils::make_unique(mesh); +#else + laplacexy = bout::utils::make_unique(mesh); +#endif + // Set coefficients for Boussinesq solve + laplacexy->setCoefs(1.0, 0.0); + phi2D = 0.0; // Starting guess + phi2D.setBoundary("phi"); + } - if (vac_lund > 0.0) { - output.write(" Vacuum Tau_R = {:e} s eta = {:e} Ohm m\n", vac_lund * Tbar, - MU0 * Lbar * Lbar / (vac_lund * Tbar)); - vac_resist = 1. / vac_lund; - } else { - output.write(" Vacuum - Zero resistivity -\n"); - vac_resist = 0.0; - } - if (core_lund > 0.0) { - output.write(" Core Tau_R = {:e} s eta = {:e} Ohm m\n", - core_lund * Tbar, MU0 * Lbar * Lbar / (core_lund * Tbar)); - core_resist = 1. / core_lund; - } else { - output.write(" Core - Zero resistivity -\n"); - core_resist = 0.0; - } + // Radial smoothing + smooth_j_x = options["smooth_j_x"].doc("Smooth Jpar in x").withDefault(false); - if (hyperresist > 0.0) { - output.write(" Hyper-resistivity coefficient: {:e}\n", hyperresist); - } + // Jpar boundary region + jpar_bndry_width = options["jpar_bndry_width"] + .doc("Number of cells near the boundary where jpar = 0") + .withDefault(-1); - if (ehyperviscos > 0.0) { - output.write(" electron Hyper-viscosity coefficient: {:e}\n", ehyperviscos); - } + sheath_boundaries = options["sheath_boundaries"] + .doc("Apply sheath boundaries in Y?") + .withDefault(false); - if (hyperviscos > 0.0) { - output.write(" Hyper-viscosity coefficient: {:e}\n", hyperviscos); - SAVE_ONCE(hyper_mu_x); - } + // Parallel differencing + parallel_lr_diff = + options["parallel_lr_diff"] + .doc("Use left and right shifted stencils for parallel differences?") + .withDefault(false); + + // RMP-related options + include_rmp = options["include_rmp"] + .doc("Read RMP field rmp_A from grid?") + .withDefault(false); + + simple_rmp = + options["simple_rmp"].doc("Include a simple RMP model?").withDefault(false); + rmp_factor = options["rmp_factor"].withDefault(1.0); + rmp_ramp = options["rmp_ramp"].withDefault(-1.0); + rmp_freq = options["rmp_freq"].withDefault(-1.0); + rmp_rotate = options["rmp_rotate"].withDefault(0.0); + + // Vacuum region control + vacuum_pressure = + options["vacuum_pressure"] + .doc("Fraction of peak pressure, below which is considered vacuum.") + .withDefault(0.02); + vacuum_trans = + options["vacuum_trans"] + .doc("Vacuum boundary transition width, as fraction of peak pressure.") + .withDefault(0.005); + + // Resistivity and hyper-resistivity options + vac_lund = + options["vac_lund"].doc("Lundquist number in vacuum region").withDefault(0.0); + core_lund = + options["core_lund"].doc("Lundquist number in core region").withDefault(0.0); + hyperresist = + options["hyperresist"].doc("Hyper-resistivity coefficient").withDefault(-1.0); + ehyperviscos = options["ehyperviscos"] + .doc("electron Hyper-viscosity coefficient") + .withDefault(-1.0); + spitzer_resist = options["spitzer_resist"] + .doc("Use Spitzer resistivity?") + .withDefault(false); + Zeff = options["Zeff"].withDefault(2.0); // Z effective + + // Inner boundary damping + damp_width = options["damp_width"] + .doc("Width of the radial damping regions, in grid cells") + .withDefault(0); + damp_t_const = + options["damp_t_const"] + .doc("Time constant for damping in radial regions. Normalised time units.") + .withDefault(0.1); + + // Viscosity and hyper-viscosity + viscos_par = options["viscos_par"].doc("Parallel viscosity").withDefault(-1.0); + viscos_perp = options["viscos_perp"].doc("Perpendicular viscosity").withDefault(-1.0); + hyperviscos = options["hyperviscos"].doc("Radial hyperviscosity").withDefault(-1.0); + + diffusion_par = + options["diffusion_par"].doc("Parallel pressure diffusion").withDefault(-1.0); + diffusion_p4 = options["diffusion_p4"] + .doc("parallel hyper-viscous diffusion for pressure") + .withDefault(-1.0); + diffusion_u4 = options["diffusion_u4"] + .doc("parallel hyper-viscous diffusion for vorticity") + .withDefault(-1.0); + diffusion_a4 = options["diffusion_a4"] + .doc("parallel hyper-viscous diffusion for vector potential") + .withDefault(-1.0); + + // heating factor in pressure + // heating power in pressure + heating_P = options["heating_P"].withDefault(-1.0); + // the percentage of radial grid points for heating profile radial + // width in pressure + hp_width = options["hp_width"].withDefault(0.1); + // the percentage of radial grid points for heating profile radial + // domain in pressure + hp_length = options["hp_length"].withDefault(0.04); + + // sink factor in pressure + // sink in pressure + sink_P = options["sink_P"].withDefault(-1.0); + // the percentage of radial grid points for sink profile radial + // width in pressure + sp_width = options["sp_width"].withDefault(0.05); + // the percentage of radial grid points for sink profile radial + // domain in pressure + sp_length = options["sp_length"].withDefault(0.04); + + // left edge sink factor in vorticity + // left edge sink in vorticity + sink_Ul = options["sink_Ul"].withDefault(-1.0); + // the percentage of left edge radial grid points for sink profile + // radial width in vorticity + su_widthl = options["su_widthl"].withDefault(0.06); + // the percentage of left edge radial grid points for sink profile + // radial domain in vorticity + su_lengthl = options["su_lengthl"].withDefault(0.15); + + // right edge sink factor in vorticity + // right edge sink in vorticity + sink_Ur = options["sink_Ur"].withDefault(-1.0); + // the percentage of right edge radial grid points for sink profile + // radial width in vorticity + su_widthr = options["su_widthr"].withDefault(0.06); + // the percentage of right edge radial grid points for sink profile + // radial domain in vorticity + su_lengthr = options["su_lengthr"].withDefault(0.15); + + // Compressional terms + phi_curv = + options["phi_curv"].doc("ExB compression in P equation?").withDefault(true); + g = options["gamma"].doc("Ratio of specific heats").withDefault(5.0 / 3.0); + + x = (Psixy - Psiaxis) / (Psibndry - Psiaxis); + + if (experiment_Er) { // get er from experiment + mesh->get(Dphi0, "Epsi"); + diamag_phi0 = false; + K_H_term = false; + } else { + Dphi0 = -D_min - 0.5 * D_0 * (1.0 - tanh(D_s * (x - x0))); + } - if (diffusion_par > 0.0) { - output.write(" diffusion_par: {:e}\n", diffusion_par); - SAVE_ONCE(diffusion_par); - } + if (sign < 0) { // change flow direction + Dphi0 *= -1; + } - // xqx: parallel hyper-viscous diffusion for pressure - if (diffusion_p4 > 0.0) { - output.write(" diffusion_p4: {:e}\n", diffusion_p4); - SAVE_ONCE(diffusion_p4); - } + if (mesh->get(Bbar, "bmag") != 0) { // Typical magnetic field + Bbar = 1.0; + } + if (mesh->get(Lbar, "rmag") != 0) { // Typical length scale + Lbar = 1.0; + } - // xqx: parallel hyper-viscous diffusion for vorticity - if (diffusion_u4 > 0.0) { - output.write(" diffusion_u4: {:e}\n", diffusion_u4); - SAVE_ONCE(diffusion_u4) - } + ////////////////////////////////////////////////////////////// + // SHIFTED RADIAL COORDINATES - // xqx: parallel hyper-viscous diffusion for vector potential - if (diffusion_a4 > 0.0) { - output.write(" diffusion_a4: {:e}\n", diffusion_a4); - SAVE_ONCE(diffusion_a4); - } + BoutReal shearFactor = 1.0; + if (!noshear && mesh->IncIntShear) { + // BOUT-06 style, using d/dx = d/dpsi + I * d/dz + metric->setIntShiftTorsion(tokamak_options.I); - if (heating_P > 0.0) { - output.write(" heating_P(watts): {:e}\n", heating_P); + } else { + // Dimits style, using local coordinate system + if (include_curvature) { + b0xcv.z += tokamak_options.I * b0xcv.x; + } + shearFactor = 0.0; // I disappears from metric + } - output.write(" hp_width(%%): {:e}\n", hp_width); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lbar, Bbar, shearFactor); - output.write(" hp_length(%%): {:e}\n", hp_length); + auto Bpxy = tokamak_options.Bpxy; + auto hthe = tokamak_options.hthe; + auto Rxy = tokamak_options.Rxy; + auto Btxy = tokamak_options.Btxy; + auto B0 = tokamak_options.Bxy; - SAVE_ONCE(heating_P, hp_width, hp_length); - } + V0 = -Rxy * Bpxy * Dphi0 / B0; - if (sink_P > 0.0) { - output.write(" sink_P(rate): {:e}\n", sink_P); - output.write(" sp_width(%%): {:e}\n", sp_width); - output.write(" sp_length(%%): {:e}\n", sp_length); + if (simple_rmp) { + include_rmp = true; + } - SAVE_ONCE(sink_P, sp_width, sp_length); - } + // toroidal and poloidal mode numbers + const int rmp_n = + options["rmp_n"].doc("Simple RMP toroidal mode number").withDefault(3); + const int rmp_m = + options["rmp_m"].doc("Simple RMP poloidal mode number").withDefault(9); + const int rmp_polwid = options["rmp_polwid"] + .doc("Poloidal width (-ve -> full, fraction of 2pi)") + .withDefault(-1.0); + const int rmp_polpeak = options["rmp_polpeak"] + .doc("Peak poloidal location (fraction of 2pi)") + .withDefault(0.5); + rmp_vac_mask = + options["rmp_vac_mask"].doc("Should a vacuum mask be applied?").withDefault(true); + // Divide n by the size of the domain + const int zperiod = globalOptions["zperiod"].withDefault(1); + + if (include_rmp) { + // Including external field coils. + if (simple_rmp) { + // Use a fairly simple form for the perturbation + Field2D pol_angle; + if (mesh->get(pol_angle, "pol_angle") != 0) { + output_warn.write(" ***WARNING: need poloidal angle for simple RMP\n"); + include_rmp = false; + } else { + if ((rmp_n % zperiod) != 0) { + output_warn.write( + " ***WARNING: rmp_n ({:d}) not a multiple of zperiod ({:d})\n", rmp_n, + zperiod); + } + + output.write("\tMagnetic perturbation: n = {:d}, m = {:d}, magnitude {:e} Tm\n", + rmp_n, rmp_m, rmp_factor); + + rmp_Psi0 = 0.0; + if (mesh->lastX()) { + // Set the outer boundary + for (int jx = mesh->LocalNx - 4; jx < mesh->LocalNx; jx++) { + for (int jy = 0; jy < mesh->LocalNy; jy++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + + BoutReal angle = + rmp_m * pol_angle(jx, jy) + + rmp_n * ((BoutReal) jz) * mesh->getCoordinates()->dz(jx, jy, jz); + rmp_Psi0(jx, jy, jz) = + (((BoutReal) (jx - 4)) / ((BoutReal) (mesh->LocalNx - 5))) + * rmp_factor * cos(angle); + if (rmp_polwid > 0.0) { + // Multiply by a Gaussian in poloidal angle + BoutReal gx = + ((pol_angle(jx, jy) / (2. * PI)) - rmp_polpeak) / rmp_polwid; + rmp_Psi0(jx, jy, jz) *= exp(-gx * gx); + } + } + } + } + } + + // Now have a simple model for Psi due to coils at the outer boundary + // Need to calculate Psi inside the domain, enforcing j = 0 + + Jpar = 0.0; + auto psiLap = std::unique_ptr{Laplacian::create(nullptr, loc)}; + psiLap->setInnerBoundaryFlags(INVERT_AC_GRAD); // Zero gradient inner BC + psiLap->setOuterBoundaryFlags(INVERT_SET); // Set to rmp_Psi0 on outer boundary + rmp_Psi0 = psiLap->solve(Jpar, rmp_Psi0); + mesh->communicate(rmp_Psi0); + } + } else { + // Load perturbation from grid file. + include_rmp = mesh->get(rmp_Psi0, "rmp_A") == 0; // Only include if found + if (!include_rmp) { + output_warn.write("WARNING: Couldn't read 'rmp_A' from grid file\n"); + } + // Multiply by factor + rmp_Psi0 *= rmp_factor; + } + } - if (K_H_term) { - output.write(" keep K-H term\n"); - } else { - output.write(" drop K-H term\n"); - } + if (!include_curvature) { + b0xcv = 0.0; + } - Field2D Te; - Te = P0 / (2.0 * density * 1.602e-19); // Temperature in eV - - J0 = -MU0 * Lbar * J0 / B0; - P0 = 2.0 * MU0 * P0 / (Bbar * Bbar); - V0 = V0 / Va; - Dphi0 *= Tbar; - - b0xcv.x /= Bbar; - b0xcv.y *= Lbar * Lbar; - b0xcv.z *= Lbar * Lbar; - - Rxy /= Lbar; - Bpxy /= Bbar; - Btxy /= Bbar; - B0 /= Bbar; - hthe /= Lbar; - metric->setDx(metric->dx() / (Lbar * Lbar * Bbar)); - I *= Lbar * Lbar * Bbar; - - if (constn0) { - T0_fake_prof = false; - n0_fake_prof = false; - } else { - Nbar = 1.0; - Tibar = 1000.0; - Tebar = 1000.0; - - if ((!T0_fake_prof) && n0_fake_prof) { - N0 = N0tanh(n0_height * Nbar, n0_ave * Nbar, n0_width, n0_center, n0_bottom_x); - - Ti0 = P0 / N0 / 2.0; - Te0 = Ti0; - } else if (T0_fake_prof) { - Ti0 = Tconst; - Te0 = Ti0; - N0 = P0 / (Ti0 + Te0); - } else { - if (mesh->get(N0, "Niexp") != 0) { // N_i0 - throw BoutException("Error: Cannot read Ni0 from grid\n"); - } - - if (mesh->get(Ti0, "Tiexp") != 0) { // T_i0 - throw BoutException("Error: Cannot read Ti0 from grid\n"); - } - - if (mesh->get(Te0, "Teexp") != 0) { // T_e0 - throw BoutException("Error: Cannot read Te0 from grid\n"); - } - N0 /= Nbar; - Ti0 /= Tibar; - Te0 /= Tebar; - } - } + if (!include_jpar0) { + J0 = 0.0; + } - if (gyroviscous) { - Dperp2Phi0.setLocation(CELL_CENTRE); - Dperp2Phi0.setBoundary("phi"); - Dperp2Phi.setLocation(CELL_CENTRE); - Dperp2Phi.setBoundary("phi"); - GradPhi02.setLocation(CELL_CENTRE); - GradPhi02.setBoundary("phi"); - GradcPhi.setLocation(CELL_CENTRE); - GradcPhi.setBoundary("phi"); - Dperp2Pi0.setLocation(CELL_CENTRE); - Dperp2Pi0.setBoundary("P"); - Dperp2Pi.setLocation(CELL_CENTRE); - Dperp2Pi.setBoundary("P"); - bracketPhi0P.setLocation(CELL_CENTRE); - bracketPhi0P.setBoundary("P"); - bracketPhiP0.setLocation(CELL_CENTRE); - bracketPhiP0.setBoundary("P"); - if (nonlinear) { - GradPhi2.setLocation(CELL_CENTRE); - GradPhi2.setBoundary("phi"); - bracketPhiP.setLocation(CELL_CENTRE); - bracketPhiP.setBoundary("P"); - } - } - BoutReal pnorm = max(P0, true); // Maximum over all processors - - vacuum_pressure *= pnorm; // Get pressure from fraction - vacuum_trans *= pnorm; - - // Transitions from 0 in core to 1 in vacuum - vac_mask = (1.0 - tanh((P0 - vacuum_pressure) / vacuum_trans)) / 2.0; - - if (spitzer_resist) { - // Use Spitzer resistivity - output.write("\tTemperature: {:e} -> {:e} [eV]\n", min(Te), max(Te)); - eta = 0.51 * 1.03e-4 * Zeff * 20. - * pow(Te, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 - output.write("\tSpitzer resistivity: {:e} -> {:e} [Ohm m]\n", min(eta), max(eta)); - eta /= MU0 * Va * Lbar; - output.write("\t -> Lundquist {:e} -> {:e}\n", 1.0 / max(eta), 1.0 / min(eta)); - } else { - // transition from 0 for large P0 to resistivity for small P0 - eta = core_resist + (vac_resist - core_resist) * vac_mask; - } + ////////////////////////////////////////////////////////////// + // NORMALISE QUANTITIES - eta = interp_to(eta, loc); - - SAVE_ONCE(eta); - - if (include_rmp) { - // Normalise RMP quantities - - rmp_Psi0 /= Bbar * Lbar; - - rmp_ramp /= Tbar; - rmp_freq *= Tbar; - rmp_rotate *= Tbar; - - rmp_Psi = rmp_Psi0; - rmp_dApdt = 0.0; - - bool apar_changing = false; - - output.write("Including magnetic perturbation\n"); - if (rmp_ramp > 0.0) { - output.write("\tRamping up over period t = {:e} ({:e} ms)\n", rmp_ramp, - rmp_ramp * Tbar * 1000.); - apar_changing = true; - } - if (rmp_freq > 0.0) { - output.write("\tOscillating with frequency f = {:e} ({:e} kHz)\n", rmp_freq, - rmp_freq / Tbar / 1000.); - apar_changing = true; - } - if (rmp_rotate != 0.0) { - output.write("\tRotating with a frequency f = {:e} ({:e} kHz)\n", rmp_rotate, - rmp_rotate / Tbar / 1000.); - apar_changing = true; - } - - if (apar_changing) { - SAVE_REPEAT(rmp_Psi, rmp_dApdt); - } else { - SAVE_ONCE(rmp_Psi); - } - } else { - rmp_Psi = 0.0; - } + Va = sqrt(Bbar * Bbar / (MU0 * density * Mi)); - /**************** CALCULATE METRICS ******************/ + Tbar = Lbar / Va; - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(I) * g11 + SQ(B0) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); + dnorm = dia_fact * Mi / (2. * 1.602e-19 * Bbar * Tbar); - const auto g_11 = 1.0 / g11 + SQ(I * Rxy); - const auto g_22 = SQ(B0 * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; + delta_i = AA * 60.67 * 5.31e5 / sqrt(density / 1e6) / (Lbar * 100.0); - metric->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); + output.write("Normalisations: Bbar = {:e} T Lbar = {:e} m\n", Bbar, Lbar); + output.write(" Va = {:e} m/s Tbar = {:e} s\n", Va, Tbar); + output.write(" dnorm = {:e}\n", dnorm); + output.write(" Resistivity\n"); - metric->setJ(hthe / Bpxy); - metric->setBxy(B0); + if (gyroviscous) { + omega_i = 9.58e7 * Zeff * Bbar; + Upara2 = 0.5 / (Tbar * omega_i); + output.write("Upara2 = {:e} Omega_i = {:e}\n", Upara2, omega_i); + } - // Set B field vector + if (eHall) { + output.write(" delta_i = {:e} AA = {:e} \n", delta_i, AA); + } - B0vec.covariant = false; - B0vec.x = 0.; - B0vec.y = Bpxy / hthe; - B0vec.z = 0.; + if (vac_lund > 0.0) { + output.write(" Vacuum Tau_R = {:e} s eta = {:e} Ohm m\n", vac_lund * Tbar, + MU0 * Lbar * Lbar / (vac_lund * Tbar)); + vac_resist = 1. / vac_lund; + } else { + output.write(" Vacuum - Zero resistivity -\n"); + vac_resist = 0.0; + } + if (core_lund > 0.0) { + output.write(" Core Tau_R = {:e} s eta = {:e} Ohm m\n", + core_lund * Tbar, MU0 * Lbar * Lbar / (core_lund * Tbar)); + core_resist = 1. / core_lund; + } else { + output.write(" Core - Zero resistivity -\n"); + core_resist = 0.0; + } - V0net.covariant = false; // presentation for net flow - V0net.x = 0.; - V0net.y = Rxy * Btxy * Bpxy / (hthe * B0 * B0) * Dphi0; - V0net.z = -Dphi0; + if (hyperresist > 0.0) { + output.write(" Hyper-resistivity coefficient: {:e}\n", hyperresist); + } - U0 = B0vec * Curl(V0net) / B0; // get 0th vorticity for Kelvin-Holmholtz term + if (ehyperviscos > 0.0) { + output.write(" electron Hyper-viscosity coefficient: {:e}\n", ehyperviscos); + } - /**************** SET EVOLVING VARIABLES *************/ + if (hyperviscos > 0.0) { + output.write(" Hyper-viscosity coefficient: {:e}\n", hyperviscos); + SAVE_ONCE(hyper_mu_x); + } - // Tell BOUT which variables to evolve - SOLVE_FOR(U, P); + if (diffusion_par > 0.0) { + output.write(" diffusion_par: {:e}\n", diffusion_par); + SAVE_ONCE(diffusion_par); + } - if (evolve_jpar) { - output.write("Solving for jpar: Inverting to get Psi\n"); - SOLVE_FOR(Jpar); - SAVE_REPEAT(Psi); - } else { - output.write("Solving for Psi, Differentiating to get jpar\n"); - SOLVE_FOR(Psi); - SAVE_REPEAT(Jpar); - } + // xqx: parallel hyper-viscous diffusion for pressure + if (diffusion_p4 > 0.0) { + output.write(" diffusion_p4: {:e}\n", diffusion_p4); + SAVE_ONCE(diffusion_p4); + } - if (compress) { - output.write("Including compression (Vpar) effects\n"); + // xqx: parallel hyper-viscous diffusion for vorticity + if (diffusion_u4 > 0.0) { + output.write(" diffusion_u4: {:e}\n", diffusion_u4); + SAVE_ONCE(diffusion_u4) + } + + // xqx: parallel hyper-viscous diffusion for vector potential + if (diffusion_a4 > 0.0) { + output.write(" diffusion_a4: {:e}\n", diffusion_a4); + SAVE_ONCE(diffusion_a4); + } - SOLVE_FOR(Vpar); - comms.add(Vpar); + if (heating_P > 0.0) { + output.write(" heating_P(watts): {:e}\n", heating_P); - beta = B0 * B0 / (0.5 + (B0 * B0 / (g * P0))); - gradparB = Grad_par(B0) / B0; + output.write(" hp_width(%%): {:e}\n", hp_width); - output.write("Beta in range {:e} -> {:e}\n", min(beta), max(beta)); - } else { - Vpar = 0.0; - } + output.write(" hp_length(%%): {:e}\n", hp_length); - if (phi_constraint) { - // Implicit Phi solve using IDA + SAVE_ONCE(heating_P, hp_width, hp_length); + } - if (!solver->constraints()) { - throw BoutException("Cannot constrain. Run again with phi_constraint=false.\n"); - } + if (sink_P > 0.0) { + output.write(" sink_P(rate): {:e}\n", sink_P); + output.write(" sp_width(%%): {:e}\n", sp_width); + output.write(" sp_length(%%): {:e}\n", sp_length); - solver->constraint(phi, C_phi, "phi"); + SAVE_ONCE(sink_P, sp_width, sp_length); + } - // Set preconditioner - setPrecon(&ELMpb::precon_phi); + if (K_H_term) { + output.write(" keep K-H term\n"); + } else { + output.write(" drop K-H term\n"); + } - } else { - // Phi solved in RHS (explicitly) - SAVE_REPEAT(phi); + Field2D Te; + Te = P0 / (2.0 * density * 1.602e-19); // Temperature in eV - // Set preconditioner - setPrecon(&ELMpb::precon); + J0 = -MU0 * Lbar * J0 / B0; + P0 = 2.0 * MU0 * P0 / (Bbar * Bbar); + V0 = V0 / Va; + Dphi0 *= Tbar; - // Set Jacobian - setJacobian((jacobianfunc)&ELMpb::jacobian); - } + b0xcv.x /= Bbar; + b0xcv.y *= Lbar * Lbar; + b0xcv.z *= Lbar * Lbar; - // Diamagnetic phi0 - if (diamag_phi0) { - if (constn0) { - phi0 = -0.5 * dnorm * P0 / B0; - } else { - // Stationary equilibrium plasma. ExB velocity balances diamagnetic drift - phi0 = -0.5 * dnorm * P0 / B0 / N0; - } - SAVE_ONCE(phi0); - } else { - phi0 = 0.0; - } + if (constn0) { + T0_fake_prof = false; + n0_fake_prof = false; + } else { + Nbar = 1.0; + Tibar = 1000.0; + Tebar = 1000.0; + + if ((!T0_fake_prof) && n0_fake_prof) { + N0 = N0tanh(n0_height * Nbar, n0_ave * Nbar, n0_width, n0_center, n0_bottom_x); + + Ti0 = P0 / N0 / 2.0; + Te0 = Ti0; + } else if (T0_fake_prof) { + Ti0 = Tconst; + Te0 = Ti0; + N0 = P0 / (Ti0 + Te0); + } else { + if (mesh->get(N0, "Niexp") != 0) { // N_i0 + throw BoutException("Error: Cannot read Ni0 from grid\n"); + } - // Add some equilibrium quantities and normalisations - // everything needed to recover physical units - SAVE_ONCE(J0, P0); - SAVE_ONCE(density, Lbar, Bbar, Tbar); - SAVE_ONCE(Va, B0); - SAVE_ONCE(Dphi0, U0); - SAVE_ONCE(V0); - if (!constn0) { - SAVE_ONCE(Ti0, Te0, N0); - } + if (mesh->get(Ti0, "Tiexp") != 0) { // T_i0 + throw BoutException("Error: Cannot read Ti0 from grid\n"); + } - // Create a solver for the Laplacian - phiSolver = Laplacian::create(&globalOptions["phiSolver"]); + if (mesh->get(Te0, "Teexp") != 0) { // T_e0 + throw BoutException("Error: Cannot read Te0 from grid\n"); + } + N0 /= Nbar; + Ti0 /= Tibar; + Te0 /= Tebar; + } + } - aparSolver = Laplacian::create(&globalOptions["aparSolver"], loc); + if (gyroviscous) { + Dperp2Phi0.setLocation(CELL_CENTRE); + Dperp2Phi0.setBoundary("phi"); + Dperp2Phi.setLocation(CELL_CENTRE); + Dperp2Phi.setBoundary("phi"); + GradPhi02.setLocation(CELL_CENTRE); + GradPhi02.setBoundary("phi"); + GradcPhi.setLocation(CELL_CENTRE); + GradcPhi.setBoundary("phi"); + Dperp2Pi0.setLocation(CELL_CENTRE); + Dperp2Pi0.setBoundary("P"); + Dperp2Pi.setLocation(CELL_CENTRE); + Dperp2Pi.setBoundary("P"); + bracketPhi0P.setLocation(CELL_CENTRE); + bracketPhi0P.setBoundary("P"); + bracketPhiP0.setLocation(CELL_CENTRE); + bracketPhiP0.setBoundary("P"); + if (nonlinear) { + GradPhi2.setLocation(CELL_CENTRE); + GradPhi2.setBoundary("phi"); + bracketPhiP.setLocation(CELL_CENTRE); + bracketPhiP.setBoundary("P"); + } + } - /////////////// CHECK VACUUM /////////////////////// - // In vacuum region, initial vorticity should equal zero + BoutReal pnorm = max(P0, true); // Maximum over all processors - if (!restarting) { - // Only if not restarting: Check initial perturbation + vacuum_pressure *= pnorm; // Get pressure from fraction + vacuum_trans *= pnorm; - // Set U to zero where P0 < vacuum_pressure - U = where(P0 - vacuum_pressure, U, 0.0); + // Transitions from 0 in core to 1 in vacuum + vac_mask = (1.0 - tanh((P0 - vacuum_pressure) / vacuum_trans)) / 2.0; - if (constn0) { - ubyn = U; - // Phi should be consistent with U - phi = phiSolver->solve(ubyn); - } else { - ubyn = U / N0; - phiSolver->setCoefC(N0); - phi = phiSolver->solve(ubyn); - } + if (spitzer_resist) { + // Use Spitzer resistivity + output.write("\tTemperature: {:e} -> {:e} [eV]\n", min(Te), max(Te)); + eta = 0.51 * 1.03e-4 * Zeff * 20. + * pow(Te, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 + output.write("\tSpitzer resistivity: {:e} -> {:e} [Ohm m]\n", min(eta), max(eta)); + eta /= MU0 * Va * Lbar; + output.write("\t -> Lundquist {:e} -> {:e}\n", 1.0 / max(eta), 1.0 / min(eta)); + } else { + // transition from 0 for large P0 to resistivity for small P0 + eta = core_resist + (vac_resist - core_resist) * vac_mask; + } - // if(diamag) { - // phi -= 0.5*dnorm * P / B0; - //} - } + eta = interp_to(eta, loc); - /************** SETUP COMMUNICATIONS **************/ + SAVE_ONCE(eta); - comms.add(U, P); + if (include_rmp) { + // Normalise RMP quantities - phi.setBoundary("phi"); // Set boundary conditions - tmpU2.setBoundary("U"); - tmpP2.setBoundary("P"); - tmpA2.setBoundary("J"); + rmp_Psi0 /= Bbar * Lbar; - if (evolve_jpar) { - comms.add(Jpar); - } else { - comms.add(Psi); - // otherwise Need to communicate Jpar separately - Jpar.setBoundary("J"); - } - Jpar2.setBoundary("J"); + rmp_ramp /= Tbar; + rmp_freq *= Tbar; + rmp_rotate *= Tbar; - return 0; - } + rmp_Psi = rmp_Psi0; + rmp_dApdt = 0.0; - // Parallel gradient along perturbed field-line - Field3D Grad_parP(const Field3D& f, CELL_LOC loc = CELL_DEFAULT) const { + bool apar_changing = false; - if (loc == CELL_DEFAULT) { - loc = f.getLocation(); - } + output.write("Including magnetic perturbation\n"); + if (rmp_ramp > 0.0) { + output.write("\tRamping up over period t = {:e} ({:e} ms)\n", rmp_ramp, + rmp_ramp * Tbar * 1000.); + apar_changing = true; + } + if (rmp_freq > 0.0) { + output.write("\tOscillating with frequency f = {:e} ({:e} kHz)\n", rmp_freq, + rmp_freq / Tbar / 1000.); + apar_changing = true; + } + if (rmp_rotate != 0.0) { + output.write("\tRotating with a frequency f = {:e} ({:e} kHz)\n", rmp_rotate, + rmp_rotate / Tbar / 1000.); + apar_changing = true; + } - Field3D result = Grad_par(f, loc); + if (apar_changing) { + SAVE_REPEAT(rmp_Psi, rmp_dApdt); + } else { + SAVE_ONCE(rmp_Psi); + } + } else { + rmp_Psi = 0.0; + } - if (nonlinear) { - result -= bracket(interp_to(Psi, loc), f, bm_mag) * B0; + // Set B field vector - if (include_rmp) { - result -= bracket(interp_to(rmp_Psi, loc), f, bm_mag) * B0; - } - } + B0vec.covariant = false; + B0vec.x = 0.; + B0vec.y = Bpxy / hthe; + B0vec.z = 0.; - return result; - } + V0net.covariant = false; // presentation for net flow + V0net.x = 0.; + V0net.y = Rxy * Btxy * Bpxy / (hthe * B0 * B0) * Dphi0; + V0net.z = -Dphi0; - bool first_run = true; // For printing out some diagnostics first time around + U0 = B0vec * Curl(V0net) / B0; // get 0th vorticity for Kelvin-Holmholtz term - int rhs(BoutReal t) override { - // Perform communications - mesh->communicate(comms); + /**************** SET EVOLVING VARIABLES *************/ - Coordinates* metric = mesh->getCoordinates(); + // Tell BOUT which variables to evolve + SOLVE_FOR(U, P); - //////////////////////////////////////////// - // Transitions from 0 in core to 1 in vacuum - if (nonlinear) { - vac_mask = (1.0 - tanh(((P0 + P) - vacuum_pressure) / vacuum_trans)) / 2.0; + if (evolve_jpar) { + output.write("Solving for jpar: Inverting to get Psi\n"); + SOLVE_FOR(Jpar); + SAVE_REPEAT(Psi); + } else { + output.write("Solving for Psi, Differentiating to get jpar\n"); + SOLVE_FOR(Psi); + SAVE_REPEAT(Jpar); + } - // Update resistivity - if (spitzer_resist) { - // Use Spitzer formula - Field3D Te; - Te = (P0 + P) * Bbar * Bbar / (4. * MU0) / (density * 1.602e-19); // eV + if (compress) { + output.write("Including compression (Vpar) effects\n"); - // eta in Ohm-m. ln(Lambda) = 20 - eta = interp_to(0.51 * 1.03e-4 * Zeff * 20. * pow(Te, -1.5), loc); + SOLVE_FOR(Vpar); + comms.add(Vpar); - // Normalised eta - eta /= MU0 * Va * Lbar; - } else { - // Use specified core and vacuum Lundquist numbers - eta = core_resist + (vac_resist - core_resist) * vac_mask; - } - eta = interp_to(eta, loc); - } + beta = B0 * B0 / (0.5 + (B0 * B0 / (g * P0))); + gradparB = Grad_par(B0) / B0; - //////////////////////////////////////////// - // Resonant Magnetic Perturbation code + output.write("Beta in range {:e} -> {:e}\n", min(beta), max(beta)); + } else { + Vpar = 0.0; + } - if (include_rmp) { + if (phi_constraint) { + // Implicit Phi solve using IDA - if ((rmp_ramp > 0.0) || (rmp_freq > 0.0) || (rmp_rotate != 0.0)) { - // Need to update the RMP terms + if (!solver->constraints()) { + throw BoutException("Cannot constrain. Run again with phi_constraint=false.\n"); + } + + solver->constraint(phi, C_phi, "phi"); + + // Set preconditioner + setPrecon(&ELMpb::precon_phi); + + } else { + // Phi solved in RHS (explicitly) + SAVE_REPEAT(phi); - if ((rmp_ramp > 0.0) && (t < rmp_ramp)) { - // Still in ramp phase + // Set preconditioner + setPrecon(&ELMpb::precon); - rmp_Psi = (t / rmp_ramp) * rmp_Psi0; // Linear ramp + // Set Jacobian + setJacobian((jacobianfunc) & ELMpb::jacobian); + } - rmp_dApdt = rmp_Psi0 / rmp_ramp; + // Diamagnetic phi0 + if (diamag_phi0) { + if (constn0) { + phi0 = -0.5 * dnorm * P0 / B0; + } else { + // Stationary equilibrium plasma. ExB velocity balances diamagnetic drift + phi0 = -0.5 * dnorm * P0 / B0 / N0; + } + SAVE_ONCE(phi0); } else { - rmp_Psi = rmp_Psi0; - rmp_dApdt = 0.0; + phi0 = 0.0; } - if (rmp_freq > 0.0) { - // Oscillating the amplitude + // Add some equilibrium quantities and normalisations + // everything needed to recover physical units + SAVE_ONCE(J0, P0); + SAVE_ONCE(density, Lbar, Bbar, Tbar); + SAVE_ONCE(Va, B0); + SAVE_ONCE(Dphi0, U0); + SAVE_ONCE(V0); + if (!constn0) { + SAVE_ONCE(Ti0, Te0, N0); + } + + // Create a solver for the Laplacian + phiSolver = Laplacian::create(&globalOptions["phiSolver"]); + + aparSolver = Laplacian::create(&globalOptions["aparSolver"], loc); - rmp_dApdt = rmp_dApdt * sin(2. * PI * rmp_freq * t) - + rmp_Psi * (2. * PI * rmp_freq) * cos(2. * PI * rmp_freq * t); + /////////////// CHECK VACUUM /////////////////////// + // In vacuum region, initial vorticity should equal zero - rmp_Psi *= sin(2. * PI * rmp_freq * t); + if (!restarting) { + // Only if not restarting: Check initial perturbation + + // Set U to zero where P0 < vacuum_pressure + U = where(P0 - vacuum_pressure, U, 0.0); + + if (constn0) { + ubyn = U; + // Phi should be consistent with U + phi = phiSolver->solve(ubyn); + } else { + ubyn = U / N0; + phiSolver->setCoefC(N0); + phi = phiSolver->solve(ubyn); + } + + // if(diamag) { + // phi -= 0.5*dnorm * P / B0; + //} } - if (rmp_rotate != 0.0) { - // Rotate toroidally at given frequency + /************** SETUP COMMUNICATIONS **************/ - shiftZ(rmp_Psi, 2 * PI * rmp_rotate * t); - shiftZ(rmp_dApdt, 2 * PI * rmp_rotate * t); + comms.add(U, P); - // Add toroidal rotation term. CHECK SIGN + phi.setBoundary("phi"); // Set boundary conditions + tmpU2.setBoundary("U"); + tmpP2.setBoundary("P"); + tmpA2.setBoundary("J"); - rmp_dApdt += DDZ(rmp_Psi) * 2 * PI * rmp_rotate; + if (evolve_jpar) { + comms.add(Jpar); + } else { + comms.add(Psi); + // otherwise Need to communicate Jpar separately + Jpar.setBoundary("J"); } + Jpar2.setBoundary("J"); - // Set to zero in the core - if (rmp_vac_mask) { - rmp_Psi *= vac_mask; + return 0; + } + + // Parallel gradient along perturbed field-line + Field3D Grad_parP(const Field3D &f, CELL_LOC loc = CELL_DEFAULT) const { + + if (loc == CELL_DEFAULT) { + loc = f.getLocation(); } - } else { - // Set to zero in the core region - if (rmp_vac_mask) { - // Only in vacuum -> skin current -> diffuses inwards - rmp_Psi = rmp_Psi0 * vac_mask; + + Field3D result = Grad_par(f, loc); + + auto B0 = tokamak_options.Bxy; + + if (nonlinear) { + result -= bracket(interp_to(Psi, loc), f, bm_mag) * B0; + + if (include_rmp) { + result -= bracket(interp_to(rmp_Psi, loc), f, bm_mag) * B0; + } } - } - mesh->communicate(rmp_Psi); + return result; } - //////////////////////////////////////////// - // Inversion + bool first_run = true; // For printing out some diagnostics first time around - if (evolve_jpar) { - // Invert laplacian for Psi - Psi = aparSolver->solve(Jpar); - mesh->communicate(Psi); - } + int rhs(BoutReal t) override { + // Perform communications + mesh->communicate(comms); - if (phi_constraint) { - // Phi being solved as a constraint + Coordinates *metric = mesh->getCoordinates(); - Field3D Ctmp = phi; - Ctmp.setBoundary("phi"); // Look up boundary conditions for phi - Ctmp.applyBoundary(); - Ctmp -= phi; // Now contains error in the boundary + auto B0 = tokamak_options.Bxy; - C_phi = Delp2(phi) - U; // Error in the bulk - C_phi.setBoundaryTo(Ctmp); + //////////////////////////////////////////// + // Transitions from 0 in core to 1 in vacuum + if (nonlinear) { + vac_mask = (1.0 - tanh(((P0 + P) - vacuum_pressure) / vacuum_trans)) / 2.0; - } else { + // Update resistivity + if (spitzer_resist) { + // Use Spitzer formula + Field3D Te; + Te = (P0 + P) * Bbar * Bbar / (4. * MU0) / (density * 1.602e-19); // eV - if (constn0) { - if (split_n0) { - //////////////////////////////////////////// - // Boussinesq, split - // Split into axisymmetric and non-axisymmetric components - Field2D Vort2D = DC(U); // n=0 component + // eta in Ohm-m. ln(Lambda) = 20 + eta = interp_to(0.51 * 1.03e-4 * Zeff * 20. * pow(Te, -1.5), loc); - // Applies boundary condition for "phi". - phi2D.applyBoundary(t); + // Normalised eta + eta /= MU0 * Va * Lbar; + } else { + // Use specified core and vacuum Lundquist numbers + eta = core_resist + (vac_resist - core_resist) * vac_mask; + } + eta = interp_to(eta, loc); + } - // Solve axisymmetric (n=0) part - phi2D = laplacexy->solve(Vort2D, phi2D); + //////////////////////////////////////////// + // Resonant Magnetic Perturbation code - // Solve non-axisymmetric part - phi = phiSolver->solve(U - Vort2D); + if (include_rmp) { - phi += phi2D; // Add axisymmetric part - } else { - phi = phiSolver->solve(U); + if ((rmp_ramp > 0.0) || (rmp_freq > 0.0) || (rmp_rotate != 0.0)) { + // Need to update the RMP terms + + if ((rmp_ramp > 0.0) && (t < rmp_ramp)) { + // Still in ramp phase + + rmp_Psi = (t / rmp_ramp) * rmp_Psi0; // Linear ramp + + rmp_dApdt = rmp_Psi0 / rmp_ramp; + } else { + rmp_Psi = rmp_Psi0; + rmp_dApdt = 0.0; + } + + if (rmp_freq > 0.0) { + // Oscillating the amplitude + + rmp_dApdt = rmp_dApdt * sin(2. * PI * rmp_freq * t) + + rmp_Psi * (2. * PI * rmp_freq) * cos(2. * PI * rmp_freq * t); + + rmp_Psi *= sin(2. * PI * rmp_freq * t); + } + + if (rmp_rotate != 0.0) { + // Rotate toroidally at given frequency + + shiftZ(rmp_Psi, 2 * PI * rmp_rotate * t); + shiftZ(rmp_dApdt, 2 * PI * rmp_rotate * t); + + // Add toroidal rotation term. CHECK SIGN + + rmp_dApdt += DDZ(rmp_Psi) * 2 * PI * rmp_rotate; + } + + // Set to zero in the core + if (rmp_vac_mask) { + rmp_Psi *= vac_mask; + } + } else { + // Set to zero in the core region + if (rmp_vac_mask) { + // Only in vacuum -> skin current -> diffuses inwards + rmp_Psi = rmp_Psi0 * vac_mask; + } + } + + mesh->communicate(rmp_Psi); } - if (diamag) { - phi -= 0.5 * dnorm * P / B0; + //////////////////////////////////////////// + // Inversion + + if (evolve_jpar) { + // Invert laplacian for Psi + Psi = aparSolver->solve(Jpar); + mesh->communicate(Psi); } - } else { - ubyn = U / N0; - if (diamag) { - ubyn -= 0.5 * dnorm / (N0 * B0) * Delp2(P); - mesh->communicate(ubyn); + + if (phi_constraint) { + // Phi being solved as a constraint + + Field3D Ctmp = phi; + Ctmp.setBoundary("phi"); // Look up boundary conditions for phi + Ctmp.applyBoundary(); + Ctmp -= phi; // Now contains error in the boundary + + C_phi = Delp2(phi) - U; // Error in the bulk + C_phi.setBoundaryTo(Ctmp); + + } else { + + if (constn0) { + if (split_n0) { + //////////////////////////////////////////// + // Boussinesq, split + // Split into axisymmetric and non-axisymmetric components + Field2D Vort2D = DC(U); // n=0 component + + // Applies boundary condition for "phi". + phi2D.applyBoundary(t); + + // Solve axisymmetric (n=0) part + phi2D = laplacexy->solve(Vort2D, phi2D); + + // Solve non-axisymmetric part + phi = phiSolver->solve(U - Vort2D); + + phi += phi2D; // Add axisymmetric part + } else { + phi = phiSolver->solve(U); + } + + if (diamag) { + phi -= 0.5 * dnorm * P / B0; + } + } else { + ubyn = U / N0; + if (diamag) { + ubyn -= 0.5 * dnorm / (N0 * B0) * Delp2(P); + mesh->communicate(ubyn); + } + // Invert laplacian for phi + phiSolver->setCoefC(N0); + phi = phiSolver->solve(ubyn); + } + // Apply a boundary condition on phi for target plates + phi.applyBoundary(); + mesh->communicate(phi); } - // Invert laplacian for phi - phiSolver->setCoefC(N0); - phi = phiSolver->solve(ubyn); - } - // Apply a boundary condition on phi for target plates - phi.applyBoundary(); - mesh->communicate(phi); - } - if (!evolve_jpar) { - // Get J from Psi - Jpar = Delp2(Psi); - if (include_rmp) { - Jpar += Delp2(rmp_Psi); - } - - Jpar.applyBoundary(); - mesh->communicate(Jpar); - - if (jpar_bndry_width > 0) { - // Zero j in boundary regions. Prevents vorticity drive - // at the boundary - - for (int i = 0; i < jpar_bndry_width; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - if (mesh->firstX()) { - Jpar(i, j, k) = 0.0; - } - if (mesh->lastX()) { - Jpar(mesh->LocalNx - 1 - i, j, k) = 0.0; - } + if (!evolve_jpar) { + // Get J from Psi + Jpar = Delp2(Psi); + if (include_rmp) { + Jpar += Delp2(rmp_Psi); } - } - } - } - - // Smooth j in x - if (smooth_j_x) { - Jpar = smooth_x(Jpar); - Jpar.applyBoundary(); - - // Recommunicate now smoothed - mesh->communicate(Jpar); - } - - // Get Delp2(J) from J - Jpar2 = Delp2(Jpar); - - Jpar2.applyBoundary(); - mesh->communicate(Jpar2); - - if (jpar_bndry_width > 0) { - // Zero jpar2 in boundary regions. Prevents vorticity drive - // at the boundary - - for (int i = 0; i < jpar_bndry_width; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - if (mesh->firstX()) { - Jpar2(i, j, k) = 0.0; - } - if (mesh->lastX()) { - Jpar2(mesh->LocalNx - 1 - i, j, k) = 0.0; - } + + Jpar.applyBoundary(); + mesh->communicate(Jpar); + + if (jpar_bndry_width > 0) { + // Zero j in boundary regions. Prevents vorticity drive + // at the boundary + + for (int i = 0; i < jpar_bndry_width; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + if (mesh->firstX()) { + Jpar(i, j, k) = 0.0; + } + if (mesh->lastX()) { + Jpar(mesh->LocalNx - 1 - i, j, k) = 0.0; + } + } + } + } } - } - } - } - } - //////////////////////////////////////////////////// - // Sheath boundary conditions - // Normalised and linearised, since here we have only pressure - // rather than density and temperature. Applying a boundary - // to Jpar so that Jpar = sqrt(mi/me)/(2*pi) * phi - // + // Smooth j in x + if (smooth_j_x) { + Jpar = smooth_x(Jpar); + Jpar.applyBoundary(); - if (sheath_boundaries) { + // Recommunicate now smoothed + mesh->communicate(Jpar); + } - // At y = ystart (lower boundary) + // Get Delp2(J) from J + Jpar2 = Delp2(Jpar); + + Jpar2.applyBoundary(); + mesh->communicate(Jpar2); + + if (jpar_bndry_width > 0) { + // Zero jpar2 in boundary regions. Prevents vorticity drive + // at the boundary + + for (int i = 0; i < jpar_bndry_width; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + if (mesh->firstX()) { + Jpar2(i, j, k) = 0.0; + } + if (mesh->lastX()) { + Jpar2(mesh->LocalNx - 1 - i, j, k) = 0.0; + } + } + } + } + } + } - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { + //////////////////////////////////////////////////// + // Sheath boundary conditions + // Normalised and linearised, since here we have only pressure + // rather than density and temperature. Applying a boundary + // to Jpar so that Jpar = sqrt(mi/me)/(2*pi) * phi + // - // Zero-gradient potential - BoutReal phisheath = phi(r.ind, mesh->ystart, jz); + if (sheath_boundaries) { - BoutReal jsheath = -(sqrt(mi_me) / (2. * sqrt(PI))) * phisheath; + // At y = ystart (lower boundary) - // Apply boundary condition half-way between cells - for (int jy = mesh->ystart - 1; jy >= 0; jy--) { - // Neumann conditions - P(r.ind, jy, jz) = P(r.ind, mesh->ystart, jz); - phi(r.ind, jy, jz) = phisheath; - // Dirichlet condition on Jpar - Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->ystart, jz); - } - } - } + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { - // At y = yend (upper boundary) + // Zero-gradient potential + BoutReal phisheath = phi(r.ind, mesh->ystart, jz); - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { + BoutReal jsheath = -(sqrt(mi_me) / (2. * sqrt(PI))) * phisheath; - // Zero-gradient potential - BoutReal phisheath = phi(r.ind, mesh->yend, jz); + // Apply boundary condition half-way between cells + for (int jy = mesh->ystart - 1; jy >= 0; jy--) { + // Neumann conditions + P(r.ind, jy, jz) = P(r.ind, mesh->ystart, jz); + phi(r.ind, jy, jz) = phisheath; + // Dirichlet condition on Jpar + Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->ystart, jz); + } + } + } + + // At y = yend (upper boundary) - BoutReal jsheath = (sqrt(mi_me) / (2. * sqrt(PI))) * phisheath; + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { - // Apply boundary condition half-way between cells - for (int jy = mesh->yend + 1; jy < mesh->LocalNy; jy++) { - // Neumann conditions - P(r.ind, jy, jz) = P(r.ind, mesh->yend, jz); - phi(r.ind, jy, jz) = phisheath; - // Dirichlet condition on Jpar - // WARNING: this is not correct if staggered grids are used - ASSERT3(not mesh->StaggerGrids); - Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->yend, jz); - } + // Zero-gradient potential + BoutReal phisheath = phi(r.ind, mesh->yend, jz); + + BoutReal jsheath = (sqrt(mi_me) / (2. * sqrt(PI))) * phisheath; + + // Apply boundary condition half-way between cells + for (int jy = mesh->yend + 1; jy < mesh->LocalNy; jy++) { + // Neumann conditions + P(r.ind, jy, jz) = P(r.ind, mesh->yend, jz); + phi(r.ind, jy, jz) = phisheath; + // Dirichlet condition on Jpar + // WARNING: this is not correct if staggered grids are used + ASSERT3(not mesh->StaggerGrids); + Jpar(r.ind, jy, jz) = 2. * jsheath - Jpar(r.ind, mesh->yend, jz); + } + } + } } - } - } - //////////////////////////////////////////////////// - // Check that settings match compile-time switches - - CHECK_SETTING(EVOLVE_JPAR, evolve_jpar); - CHECK_SETTING(RELAX_J_VAC, relax_j_vac); - CHECK_SETTING(EHALL, eHall); - CHECK_SETTING(DIAMAG_PHI0, diamag_phi0); - CHECK_SETTING(DIAMAG_GRAD_T, diamag_grad_t); - CHECK_SETTING(HYPERRESIST, hyperresist > 0.0); // Check that it is enabled or disabled - CHECK_SETTING(EHYPERVISCOS, ehyperviscos > 0.0); - CHECK_SETTING(INCLUDE_RMP, include_rmp); - CHECK_SETTING(GRADPARJ, !nogradparj); // Note: Can't negate macro argument - CHECK_SETTING(VISCOS_PERP, viscos_perp > 0.0); - CHECK_SETTING(EVOLVE_PRESSURE, evolve_pressure); - CHECK_SETTING(NONLINEAR, nonlinear); - - //////////////////////////////////////////////////// - // Create accessors for direct access to the data - - // Equilibrium (2D) fields - auto P0_acc = Field2DAccessor<>(P0); - auto J0_acc = Field2DAccessor<>(J0); - auto phi0_acc = Field2DAccessor<>(phi0); - auto B0_acc = Field2DAccessor<>(B0); - - // Evolving fields - auto P_acc = FieldAccessor<>(P); - auto Psi_acc = FieldAccessor<>(Psi); - auto U_acc = FieldAccessor<>(U); - - // Derived fields - auto Jpar_acc = FieldAccessor<>(Jpar); - auto phi_acc = FieldAccessor<>(phi); - auto eta_acc = FieldAccessor<>(eta); + //////////////////////////////////////////////////// + // Check that settings match compile-time switches + + CHECK_SETTING(EVOLVE_JPAR, evolve_jpar); + CHECK_SETTING(RELAX_J_VAC, relax_j_vac); + CHECK_SETTING(EHALL, eHall); + CHECK_SETTING(DIAMAG_PHI0, diamag_phi0); + CHECK_SETTING(DIAMAG_GRAD_T, diamag_grad_t); + CHECK_SETTING(HYPERRESIST, hyperresist > 0.0); // Check that it is enabled or disabled + CHECK_SETTING(EHYPERVISCOS, ehyperviscos > 0.0); + CHECK_SETTING(INCLUDE_RMP, include_rmp); + CHECK_SETTING(GRADPARJ, !nogradparj); // Note: Can't negate macro argument + CHECK_SETTING(VISCOS_PERP, viscos_perp > 0.0); + CHECK_SETTING(EVOLVE_PRESSURE, evolve_pressure); + CHECK_SETTING(NONLINEAR, nonlinear); + + //////////////////////////////////////////////////// + // Create accessors for direct access to the data + + // Equilibrium (2D) fields + auto P0_acc = Field2DAccessor<>(P0); + auto J0_acc = Field2DAccessor<>(J0); + auto phi0_acc = Field2DAccessor<>(phi0); + auto B0_acc = Field2DAccessor<>(B0); + + // Evolving fields + auto P_acc = FieldAccessor<>(P); + auto Psi_acc = FieldAccessor<>(Psi); + auto U_acc = FieldAccessor<>(U); + + // Derived fields + auto Jpar_acc = FieldAccessor<>(Jpar); + auto phi_acc = FieldAccessor<>(phi); + auto eta_acc = FieldAccessor<>(eta); #if EHYPERVISCOS - auto Jpar2_acc = FieldAccessor<>(Jpar2); + auto Jpar2_acc = FieldAccessor<>(Jpar2); #endif #if EVOLVE_JPAR - Field3D B0U = B0 * U; - mesh->communicate(B0U); - auto B0U_acc = FieldAccessor<>(B0U); + Field3D B0U = B0 * U; + mesh->communicate(B0U); + auto B0U_acc = FieldAccessor<>(B0U); #else - Field3D B0phi = B0 * phi; - mesh->communicate(B0phi); - auto B0phi_acc = FieldAccessor<>(B0phi); + Field3D B0phi = B0 * phi; + mesh->communicate(B0phi); + auto B0phi_acc = FieldAccessor<>(B0phi); #if EHALL - Field3D B0P = B0 * P; - mesh->communicate(B0 * P); - auto B0P_acc = FieldAccessor<>(B0P); + Field3D B0P = B0 * P; + mesh->communicate(B0 * P); + auto B0P_acc = FieldAccessor<>(B0P); #endif // EHALL #endif // EVOLVE_JPAR #if RELAX_J_VAC - auto vac_mask_acc = FieldAccessor<>(vac_mask); + auto vac_mask_acc = FieldAccessor<>(vac_mask); #endif #if INCLUDE_RMP - auto rmp_Psi_acc = FieldAccessor<>(rmp_Psi); + auto rmp_Psi_acc = FieldAccessor<>(rmp_Psi); #endif - //////////////////////////////////////////////////// - // Start loop over a region of the mesh - // If RAJA is not available, this will fall back to BOUT_FOR - // - // Note: Capture all class member variables into local scope - // or an illegal memory access may occur on GPUs + //////////////////////////////////////////////////// + // Start loop over a region of the mesh + // If RAJA is not available, this will fall back to BOUT_FOR + // + // Note: Capture all class member variables into local scope + // or an illegal memory access may occur on GPUs - BOUT_FOR_RAJA( - i, Jpar.getRegion("RGN_NOBNDRY"), - CAPTURE(delta_i, hyperresist, relax_j_tconst, dnorm, ehyperviscos, viscos_perp)) { - int i2d = static_cast(i) / Jpar_acc.mesh_nz; // An index for 2D objects + BOUT_FOR_RAJA( + i, Jpar.getRegion("RGN_NOBNDRY"), + CAPTURE(delta_i, hyperresist, relax_j_tconst, dnorm, ehyperviscos, viscos_perp)) { + int i2d = static_cast(i) / Jpar_acc.mesh_nz; // An index for 2D objects - //////////////////////////////////////////////////// - // Parallel electric field + //////////////////////////////////////////////////// + // Parallel electric field #if EVOLVE_JPAR - // Evolving parallel current ddt(Jpar) + // Evolving parallel current ddt(Jpar) - ddt(Jpar_acc)[i] = -Grad_par(B0U_acc, i) / B0_acc[i2d] - + eta_acc[i] * Delp2(Jpar_acc, i) + ddt(Jpar_acc)[i] = -Grad_par(B0U_acc, i) / B0_acc[i2d] + + eta_acc[i] * Delp2(Jpar_acc, i) - - EVAL_IF(RELAX_J_VAC, // Relax current to zero - vac_mask_acc[i] * Jpar_acc[i] / relax_j_tconst); + - EVAL_IF(RELAX_J_VAC, // Relax current to zero + vac_mask_acc[i] * Jpar_acc[i] / relax_j_tconst); #else - // Evolve vector potential ddt(psi) - ddt(Psi_acc)[i] = -GRAD_PARP(B0phi_acc) / B0_acc[i2d] + eta_acc[i] * Jpar_acc[i] + // Evolve vector potential ddt(psi) + ddt(Psi_acc)[i] = -GRAD_PARP(B0phi_acc) / B0_acc[i2d] + eta_acc[i] * Jpar_acc[i] - + EVAL_IF(EHALL, // electron parallel pressure - 0.25 * delta_i - * (GRAD_PARP(B0P_acc) / B0_acc[i2d] - + bracket(P0_acc, Psi_acc, i) * B0_acc[i2d])) + + EVAL_IF(EHALL, // electron parallel pressure + 0.25 * delta_i + * (GRAD_PARP(B0P_acc) / B0_acc[i2d] + + bracket(P0_acc, Psi_acc, i) * B0_acc[i2d])) - - EVAL_IF(DIAMAG_PHI0, // Equilibrium flow - bracket(phi0_acc, Psi_acc, i) * B0_acc[i2d]) + - EVAL_IF(DIAMAG_PHI0, // Equilibrium flow + bracket(phi0_acc, Psi_acc, i) * B0_acc[i2d]) - + EVAL_IF(DIAMAG_GRAD_T, // grad_par(T_e) correction - 1.71 * dnorm * 0.5 * GRAD_PARP(P_acc) / B0_acc[i2d]) + + EVAL_IF(DIAMAG_GRAD_T, // grad_par(T_e) correction + 1.71 * dnorm * 0.5 * GRAD_PARP(P_acc) / B0_acc[i2d]) - - EVAL_IF(HYPERRESIST, // Hyper-resistivity - eta_acc[i] * hyperresist * Delp2(Jpar_acc, i)) + - EVAL_IF(HYPERRESIST, // Hyper-resistivity + eta_acc[i] * hyperresist * Delp2(Jpar_acc, i)) - - EVAL_IF(EHYPERVISCOS, // electron Hyper-viscosity - eta_acc[i] * ehyperviscos * Delp2(Jpar2_acc, i)); + - EVAL_IF(EHYPERVISCOS, // electron Hyper-viscosity + eta_acc[i] * ehyperviscos * Delp2(Jpar2_acc, i)); #endif - //////////////////////////////////////////////////// - // Vorticity equation + //////////////////////////////////////////////////// + // Vorticity equation - ddt(U_acc)[i] = - SQ(B0_acc[i2d]) * b0xGrad_dot_Grad(Psi_acc, J0_acc, i) + ddt(U_acc)[i] = + SQ(B0_acc[i2d]) * b0xGrad_dot_Grad(Psi_acc, J0_acc, i) - + EVAL_IF(INCLUDE_RMP, // External magnetic field perturbation - SQ(B0_acc[i2d]) * b0xGrad_dot_Grad(rmp_Psi_acc, J0_acc, i)) + + EVAL_IF(INCLUDE_RMP, // External magnetic field perturbation + SQ(B0_acc[i2d]) * b0xGrad_dot_Grad(rmp_Psi_acc, J0_acc, i)) - - EVAL_IF(GRADPARJ, // Parallel current term - SQ(B0_acc[i2d]) * GRAD_PARP(Jpar_acc)) + - EVAL_IF(GRADPARJ, // Parallel current term + SQ(B0_acc[i2d]) * GRAD_PARP(Jpar_acc)) - - EVAL_IF(DIAMAG_PHI0, // Equilibrium flow - b0xGrad_dot_Grad(phi0_acc, U_acc, i)) + - EVAL_IF(DIAMAG_PHI0, // Equilibrium flow + b0xGrad_dot_Grad(phi0_acc, U_acc, i)) - - EVAL_IF(NONLINEAR, // Advection - bracket(phi_acc, U_acc, i) * B0_acc[i2d]) + - EVAL_IF(NONLINEAR, // Advection + bracket(phi_acc, U_acc, i) * B0_acc[i2d]) - + EVAL_IF(VISCOS_PERP, // Perpendicular viscosity - viscos_perp * Delp2(U_acc, i)); + + EVAL_IF(VISCOS_PERP, // Perpendicular viscosity + viscos_perp * Delp2(U_acc, i)); - //////////////////////////////////////////////////// - // Pressure equation + //////////////////////////////////////////////////// + // Pressure equation #if EVOLVE_PRESSURE - ddt(P_acc)[i] = -b0xGrad_dot_Grad(phi_acc, P0_acc, i) + ddt(P_acc)[i] = -b0xGrad_dot_Grad(phi_acc, P0_acc, i) - - EVAL_IF(DIAMAG_PHI0, // Equilibrium flow - b0xGrad_dot_Grad(phi0_acc, P_acc, i)) + - EVAL_IF(DIAMAG_PHI0, // Equilibrium flow + b0xGrad_dot_Grad(phi0_acc, P_acc, i)) - - EVAL_IF(NONLINEAR, // Advection - bracket(phi_acc, P_acc, i) * B0_acc[i2d]); + - EVAL_IF(NONLINEAR, // Advection + bracket(phi_acc, P_acc, i) * B0_acc[i2d]); #else - ddt(P_acc)[i] = 0.0; + ddt(P_acc)[i] = 0.0; #endif - }; + }; - // Terms which are not yet single index operators - // Note: Terms which are included in the single index loop - // may be commented out here, to allow comparison/testing + // Terms which are not yet single index operators + // Note: Terms which are included in the single index loop + // may be commented out here, to allow comparison/testing - //////////////////////////////////////////////////// - // Parallel electric field + //////////////////////////////////////////////////// + // Parallel electric field #if not EVOLVE_JPAR - // Vector potential - // ddt(Psi) = -Grad_parP(phi, loc) + eta * Jpar; + // Vector potential + // ddt(Psi) = -Grad_parP(phi, loc) + eta * Jpar; - // if (eHall) { // electron parallel pressure - // ddt(Psi) += 0.25 * delta_i - // * (Grad_parP(P, loc) - // + bracket(interp_to(P0, loc), Psi, bm_mag)); - // } + // if (eHall) { // electron parallel pressure + // ddt(Psi) += 0.25 * delta_i + // * (Grad_parP(P, loc) + // + bracket(interp_to(P0, loc), Psi, bm_mag)); + // } - // if (diamag_phi0) { // Equilibrium flow - // ddt(Psi) -= bracket(interp_to(phi0, loc), Psi, bm_exb); - // } + // if (diamag_phi0) { // Equilibrium flow + // ddt(Psi) -= bracket(interp_to(phi0, loc), Psi, bm_exb); + // } - if (withflow) { // net flow - ddt(Psi) -= V_dot_Grad(V0net, Psi); - } + if (withflow) { // net flow + ddt(Psi) -= V_dot_Grad(V0net, Psi); + } - // if (diamag_grad_t) { // grad_par(T_e) correction - // ddt(Psi) += 1.71 * dnorm * 0.5 * Grad_parP(P, loc) / B0; - // } + // if (diamag_grad_t) { // grad_par(T_e) correction + // ddt(Psi) += 1.71 * dnorm * 0.5 * Grad_parP(P, loc) / B0; + // } - // if (hyperresist > 0.0) { // Hyper-resistivity - // ddt(Psi) -= eta * hyperresist * Delp2(Jpar); - // } + // if (hyperresist > 0.0) { // Hyper-resistivity + // ddt(Psi) -= eta * hyperresist * Delp2(Jpar); + // } - // if (ehyperviscos > 0.0) { // electron Hyper-viscosity coefficient - // ddt(Psi) -= eta * ehyperviscos * Delp2(Jpar2); - // } + // if (ehyperviscos > 0.0) { // electron Hyper-viscosity coefficient + // ddt(Psi) -= eta * ehyperviscos * Delp2(Jpar2); + // } - // Parallel hyper-viscous diffusion for vector potential - if (diffusion_a4 > 0.0) { - tmpA2 = D2DY2(Psi); - mesh->communicate(tmpA2); - tmpA2.applyBoundary(); - ddt(Psi) -= diffusion_a4 * D2DY2(tmpA2); - } + // Parallel hyper-viscous diffusion for vector potential + if (diffusion_a4 > 0.0) { + tmpA2 = D2DY2(Psi); + mesh->communicate(tmpA2); + tmpA2.applyBoundary(); + ddt(Psi) -= diffusion_a4 * D2DY2(tmpA2); + } - // Vacuum solution - if (relax_j_vac) { - // Calculate the J and Psi profile we're aiming for - Field3D Jtarget = Jpar * (1.0 - vac_mask); // Zero in vacuum + // Vacuum solution + if (relax_j_vac) { + // Calculate the J and Psi profile we're aiming for + Field3D Jtarget = Jpar * (1.0 - vac_mask); // Zero in vacuum - // Invert laplacian for Psi - Field3D Psitarget = aparSolver->solve(Jtarget); + // Invert laplacian for Psi + Field3D Psitarget = aparSolver->solve(Jtarget); - // Add a relaxation term in the vacuum - ddt(Psi) = - ddt(Psi) * (1. - vac_mask) - (Psi - Psitarget) * vac_mask / relax_j_tconst; - } + // Add a relaxation term in the vacuum + ddt(Psi) = + ddt(Psi) * (1. - vac_mask) - (Psi - Psitarget) * vac_mask / relax_j_tconst; + } #endif - //////////////////////////////////////////////////// - // Vorticity equation + //////////////////////////////////////////////////// + // Vorticity equation - // Grad j term - // ddt(U) = SQ(B0) * b0xGrad_dot_Grad(Psi, J0, CELL_CENTRE); + // Grad j term + // ddt(U) = SQ(B0) * b0xGrad_dot_Grad(Psi, J0, CELL_CENTRE); - // if (include_rmp) { - // ddt(U) += SQ(B0) * b0xGrad_dot_Grad(rmp_Psi, J0, CELL_CENTRE); - // } + // if (include_rmp) { + // ddt(U) += SQ(B0) * b0xGrad_dot_Grad(rmp_Psi, J0, CELL_CENTRE); + // } - ddt(U) += b0xcv * Grad(P); // curvature term + ddt(U) += b0xcv * Grad(P); // curvature term - // if (!nogradparj) { // Parallel current term - // ddt(U) -= SQ(B0) * Grad_parP(Jpar, CELL_CENTRE); // b dot grad j - // } + // if (!nogradparj) { // Parallel current term + // ddt(U) -= SQ(B0) * Grad_parP(Jpar, CELL_CENTRE); // b dot grad j + // } - if (withflow && K_H_term) { // K_H_term - ddt(U) -= b0xGrad_dot_Grad(phi, U0); - } + if (withflow && K_H_term) { // K_H_term + ddt(U) -= b0xGrad_dot_Grad(phi, U0); + } - // if (diamag_phi0) { // Equilibrium flow - // ddt(U) -= b0xGrad_dot_Grad(phi0, U); - // } + // if (diamag_phi0) { // Equilibrium flow + // ddt(U) -= b0xGrad_dot_Grad(phi0, U); + // } - if (withflow) { // net flow - ddt(U) -= V_dot_Grad(V0net, U); - } + if (withflow) { // net flow + ddt(U) -= V_dot_Grad(V0net, U); + } - // if (nonlinear) { // Advection - // ddt(U) -= bracket(phi, U, bm_exb) * B0; - // } + // if (nonlinear) { // Advection + // ddt(U) -= bracket(phi, U, bm_exb) * B0; + // } - // Viscosity terms + // Viscosity terms - if (viscos_par > 0.0) { // Parallel viscosity - ddt(U) += viscos_par * Grad2_par2(U); - } + if (viscos_par > 0.0) { // Parallel viscosity + ddt(U) += viscos_par * Grad2_par2(U); + } - if (diffusion_u4 > 0.0) { - tmpU2 = D2DY2(U); - mesh->communicate(tmpU2); - tmpU2.applyBoundary(); - ddt(U) -= diffusion_u4 * D2DY2(tmpU2); - } + if (diffusion_u4 > 0.0) { + tmpU2 = D2DY2(U); + mesh->communicate(tmpU2); + tmpU2.applyBoundary(); + ddt(U) -= diffusion_u4 * D2DY2(tmpU2); + } - // if (viscos_perp > 0.0) { // Perpendicular viscosity - // ddt(U) += viscos_perp * Delp2(U); - // } + // if (viscos_perp > 0.0) { // Perpendicular viscosity + // ddt(U) += viscos_perp * Delp2(U); + // } - // Hyper-viscosity - if (hyperviscos > 0.0) { - // Calculate coefficient. + // Hyper-viscosity + if (hyperviscos > 0.0) { + // Calculate coefficient. - hyper_mu_x = hyperviscos * metric->g_11() * SQ(metric->dx()) - * abs(metric->g11() * D2DX2(U)) / (abs(U) + 1e-3); - hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries + hyper_mu_x = hyperviscos * metric->g_11() * SQ(metric->dx()) + * abs(metric->g11() * D2DX2(U)) / (abs(U) + 1e-3); + hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries - ddt(U) += hyper_mu_x * metric->g11() * D2DX2(U); + ddt(U) += hyper_mu_x * metric->g11() * D2DX2(U); - if (first_run) { // Print out maximum values of viscosity used on this processor - output.write(" Hyper-viscosity values:\n"); - output.write(" Max mu_x = {:e}, Max_DC mu_x = {:e}\n", max(hyper_mu_x), - max(DC(hyper_mu_x))); - } - } + if (first_run) { // Print out maximum values of viscosity used on this processor + output.write(" Hyper-viscosity values:\n"); + output.write(" Max mu_x = {:e}, Max_DC mu_x = {:e}\n", max(hyper_mu_x), + max(DC(hyper_mu_x))); + } + } - if (gyroviscous) { - - Field3D Pi; - Field2D Pi0; - Pi = 0.5 * P; - Pi0 = 0.5 * P0; - - Dperp2Phi0 = Field3D(Delp2(B0 * phi0)); - Dperp2Phi0.applyBoundary(); - mesh->communicate(Dperp2Phi0); - - Dperp2Phi = Delp2(B0 * phi); - Dperp2Phi.applyBoundary(); - mesh->communicate(Dperp2Phi); - - Dperp2Pi0 = Field3D(Delp2(Pi0)); - Dperp2Pi0.applyBoundary(); - mesh->communicate(Dperp2Pi0); - - Dperp2Pi = Delp2(Pi); - Dperp2Pi.applyBoundary(); - mesh->communicate(Dperp2Pi); - - bracketPhi0P = bracket(B0 * phi0, Pi, bm_exb); - bracketPhi0P.applyBoundary(); - mesh->communicate(bracketPhi0P); - - bracketPhiP0 = bracket(B0 * phi, Pi0, bm_exb); - bracketPhiP0.applyBoundary(); - mesh->communicate(bracketPhiP0); - - ddt(U) -= 0.5 * Upara2 * bracket(Pi, Dperp2Phi0, bm_exb) / B0; - ddt(U) -= 0.5 * Upara2 * bracket(Pi0, Dperp2Phi, bm_exb) / B0; - Field3D B0phi = B0 * phi; - mesh->communicate(B0phi); - Field3D B0phi0 = B0 * phi0; - mesh->communicate(B0phi0); - ddt(U) += 0.5 * Upara2 * bracket(B0phi, Dperp2Pi0, bm_exb) / B0; - ddt(U) += 0.5 * Upara2 * bracket(B0phi0, Dperp2Pi, bm_exb) / B0; - ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhi0P) / B0; - ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhiP0) / B0; - - if (nonlinear) { - Field3D B0phi = B0 * phi; - mesh->communicate(B0phi); - bracketPhiP = bracket(B0phi, Pi, bm_exb); - bracketPhiP.applyBoundary(); - mesh->communicate(bracketPhiP); - - ddt(U) -= 0.5 * Upara2 * bracket(Pi, Dperp2Phi, bm_exb) / B0; - ddt(U) += 0.5 * Upara2 * bracket(B0phi, Dperp2Pi, bm_exb) / B0; - ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhiP) / B0; - } - } + if (gyroviscous) { + + Field3D Pi; + Field2D Pi0; + Pi = 0.5 * P; + Pi0 = 0.5 * P0; + + Dperp2Phi0 = Field3D(Delp2(B0 * phi0)); + Dperp2Phi0.applyBoundary(); + mesh->communicate(Dperp2Phi0); + + Dperp2Phi = Delp2(B0 * phi); + Dperp2Phi.applyBoundary(); + mesh->communicate(Dperp2Phi); + + Dperp2Pi0 = Field3D(Delp2(Pi0)); + Dperp2Pi0.applyBoundary(); + mesh->communicate(Dperp2Pi0); + + Dperp2Pi = Delp2(Pi); + Dperp2Pi.applyBoundary(); + mesh->communicate(Dperp2Pi); + + bracketPhi0P = bracket(B0 * phi0, Pi, bm_exb); + bracketPhi0P.applyBoundary(); + mesh->communicate(bracketPhi0P); + + bracketPhiP0 = bracket(B0 * phi, Pi0, bm_exb); + bracketPhiP0.applyBoundary(); + mesh->communicate(bracketPhiP0); + + ddt(U) -= 0.5 * Upara2 * bracket(Pi, Dperp2Phi0, bm_exb) / B0; + ddt(U) -= 0.5 * Upara2 * bracket(Pi0, Dperp2Phi, bm_exb) / B0; + Field3D B0phi = B0 * phi; + mesh->communicate(B0phi); + Field3D B0phi0 = B0 * phi0; + mesh->communicate(B0phi0); + ddt(U) += 0.5 * Upara2 * bracket(B0phi, Dperp2Pi0, bm_exb) / B0; + ddt(U) += 0.5 * Upara2 * bracket(B0phi0, Dperp2Pi, bm_exb) / B0; + ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhi0P) / B0; + ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhiP0) / B0; + + if (nonlinear) { + Field3D B0phi = B0 * phi; + mesh->communicate(B0phi); + bracketPhiP = bracket(B0phi, Pi, bm_exb); + bracketPhiP.applyBoundary(); + mesh->communicate(bracketPhiP); + + ddt(U) -= 0.5 * Upara2 * bracket(Pi, Dperp2Phi, bm_exb) / B0; + ddt(U) += 0.5 * Upara2 * bracket(B0phi, Dperp2Pi, bm_exb) / B0; + ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhiP) / B0; + } + } - // left edge sink terms - if (sink_Ul > 0.0) { - ddt(U) -= sink_Ul * sink_tanhxl(P0, U, su_widthl, su_lengthl); // core sink - } + // left edge sink terms + if (sink_Ul > 0.0) { + ddt(U) -= sink_Ul * sink_tanhxl(P0, U, su_widthl, su_lengthl); // core sink + } - // right edge sink terms - if (sink_Ur > 0.0) { - ddt(U) -= sink_Ur * sink_tanhxr(P0, U, su_widthr, su_lengthr); // sol sink - } + // right edge sink terms + if (sink_Ur > 0.0) { + ddt(U) -= sink_Ur * sink_tanhxr(P0, U, su_widthr, su_lengthr); // sol sink + } - //////////////////////////////////////////////////// - // Pressure equation + //////////////////////////////////////////////////// + // Pressure equation - if (evolve_pressure) { + if (evolve_pressure) { - // ddt(P) -= b0xGrad_dot_Grad(phi, P0); + // ddt(P) -= b0xGrad_dot_Grad(phi, P0); - // if (diamag_phi0) { // Equilibrium flow - // ddt(P) -= b0xGrad_dot_Grad(phi0, P); - // } + // if (diamag_phi0) { // Equilibrium flow + // ddt(P) -= b0xGrad_dot_Grad(phi0, P); + // } - if (withflow) { // net flow - ddt(P) -= V_dot_Grad(V0net, P); - } + if (withflow) { // net flow + ddt(P) -= V_dot_Grad(V0net, P); + } - // if (nonlinear) { // Advection - // ddt(P) -= bracket(phi, P, bm_exb) * B0; - // } + // if (nonlinear) { // Advection + // ddt(P) -= bracket(phi, P, bm_exb) * B0; + // } - // Parallel diffusion terms + // Parallel diffusion terms - if (diffusion_par > 0.0) { // Parallel diffusion - ddt(P) += diffusion_par * Grad2_par2(P); - } + if (diffusion_par > 0.0) { // Parallel diffusion + ddt(P) += diffusion_par * Grad2_par2(P); + } - if (diffusion_p4 > 0.0) { - tmpP2 = D2DY2(P); - mesh->communicate(tmpP2); - tmpP2.applyBoundary(); - ddt(P) = diffusion_p4 * D2DY2(tmpP2); - } + if (diffusion_p4 > 0.0) { + tmpP2 = D2DY2(P); + mesh->communicate(tmpP2); + tmpP2.applyBoundary(); + ddt(P) = diffusion_p4 * D2DY2(tmpP2); + } - // heating source terms - if (heating_P > 0.0) { - BoutReal pnorm = P0(0, 0); - ddt(P) += heating_P * source_expx2(P0, 2. * hp_width, 0.5 * hp_length) - * (Tbar / pnorm); // heat source - ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11() - * D2DX2(P) * (Tbar / Lbar / Lbar); // radial diffusion - } + // heating source terms + if (heating_P > 0.0) { + BoutReal pnorm = P0(0, 0); + ddt(P) += heating_P * source_expx2(P0, 2. * hp_width, 0.5 * hp_length) + * (Tbar / pnorm); // heat source + ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11() + * D2DX2(P) * (Tbar / Lbar / Lbar); // radial diffusion + } - // sink terms - if (sink_P > 0.0) { - ddt(P) -= sink_P * sink_tanhxr(P0, P, sp_width, sp_length) * Tbar; // sink - } - } + // sink terms + if (sink_P > 0.0) { + ddt(P) -= sink_P * sink_tanhxr(P0, P, sp_width, sp_length) * Tbar; // sink + } + } - //////////////////////////////////////////////////// - // Compressional effects + //////////////////////////////////////////////////// + // Compressional effects - if (compress) { + if (compress) { - ddt(P) -= beta * Div_par(Vpar, CELL_CENTRE); + ddt(P) -= beta * Div_par(Vpar, CELL_CENTRE); - if (phi_curv) { - ddt(P) -= 2. * beta * b0xcv * Grad(phi); - } + if (phi_curv) { + ddt(P) -= 2. * beta * b0xcv * Grad(phi); + } - // Vpar equation + // Vpar equation - ddt(Vpar) = -0.5 * (Grad_par(P, loc) + Grad_par(P0, loc)); + ddt(Vpar) = -0.5 * (Grad_par(P, loc) + Grad_par(P0, loc)); - if (nonlinear) { - ddt(Vpar) -= bracket(interp_to(phi, loc), Vpar, bm_exb) * B0; // Advection - } - } + if (nonlinear) { + ddt(Vpar) -= bracket(interp_to(phi, loc), Vpar, bm_exb) * B0; // Advection + } + } - if (filter_z) { - // Filter out all except filter_z_mode + if (filter_z) { + // Filter out all except filter_z_mode - if (evolve_jpar) { - ddt(Jpar) = filter(ddt(Jpar), filter_z_mode); - } else { - ddt(Psi) = filter(ddt(Psi), filter_z_mode); - } - ddt(U) = filter(ddt(U), filter_z_mode); - ddt(P) = filter(ddt(P), filter_z_mode); - } + if (evolve_jpar) { + ddt(Jpar) = filter(ddt(Jpar), filter_z_mode); + } else { + ddt(Psi) = filter(ddt(Psi), filter_z_mode); + } + ddt(U) = filter(ddt(U), filter_z_mode); + ddt(P) = filter(ddt(P), filter_z_mode); + } + + if (low_pass_z > 0) { + // Low-pass filter, keeping n up to low_pass_z + if (evolve_jpar) { + ddt(Jpar) = lowPass(ddt(Jpar), low_pass_z, zonal_field); + } else { + ddt(Psi) = lowPass(ddt(Psi), low_pass_z, zonal_field); + } + ddt(U) = lowPass(ddt(U), low_pass_z, zonal_flow); + ddt(P) = lowPass(ddt(P), low_pass_z, zonal_bkgd); + } + + if (damp_width > 0) { + for (int i = 0; i < damp_width; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + if (mesh->firstX()) { + ddt(U)(i, j, k) -= U(i, j, k) / damp_t_const; + } + if (mesh->lastX()) { + ddt(U)(mesh->LocalNx - 1 - i, j, k) -= + U(mesh->LocalNx - 1 - i, j, k) / damp_t_const; + } + } + } + } + } + + first_run = false; - if (low_pass_z > 0) { - // Low-pass filter, keeping n up to low_pass_z - if (evolve_jpar) { - ddt(Jpar) = lowPass(ddt(Jpar), low_pass_z, zonal_field); - } else { - ddt(Psi) = lowPass(ddt(Psi), low_pass_z, zonal_field); - } - ddt(U) = lowPass(ddt(U), low_pass_z, zonal_flow); - ddt(P) = lowPass(ddt(P), low_pass_z, zonal_bkgd); + return 0; } - if (damp_width > 0) { - for (int i = 0; i < damp_width; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { + /******************************************************************************* + * Preconditioner + * + * o System state in variables (as in rhs function) + * o Values to be inverted in time derivatives + * + * o Return values should be in time derivatives + * + * enable by setting solver / use_precon = true in BOUT.inp + *******************************************************************************/ + + int precon(BoutReal UNUSED(t), BoutReal gamma, BoutReal UNUSED(delta)) { + // First matrix, applying L + mesh->communicate(ddt(Psi)); + Field3D Jrhs = Delp2(ddt(Psi)); + Jrhs.applyBoundary("neumann"); + + if (jpar_bndry_width > 0) { + // Boundary in jpar if (mesh->firstX()) { - ddt(U)(i, j, k) -= U(i, j, k) / damp_t_const; + for (int i = jpar_bndry_width; i >= 0; i--) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + Jrhs(i, j, k) = 0.5 * Jrhs(i + 1, j, k); + } + } + } } if (mesh->lastX()) { - ddt(U)(mesh->LocalNx - 1 - i, j, k) -= - U(mesh->LocalNx - 1 - i, j, k) / damp_t_const; + for (int i = mesh->LocalNx - jpar_bndry_width - 1; i < mesh->LocalNx; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + Jrhs(i, j, k) = 0.5 * Jrhs(i - 1, j, k); + } + } + } } - } } - } - } - first_run = false; + mesh->communicate(Jrhs, ddt(P)); - return 0; - } + Field3D U1 = ddt(U); - /******************************************************************************* - * Preconditioner - * - * o System state in variables (as in rhs function) - * o Values to be inverted in time derivatives - * - * o Return values should be in time derivatives - * - * enable by setting solver / use_precon = true in BOUT.inp - *******************************************************************************/ - - int precon(BoutReal UNUSED(t), BoutReal gamma, BoutReal UNUSED(delta)) { - // First matrix, applying L - mesh->communicate(ddt(Psi)); - Field3D Jrhs = Delp2(ddt(Psi)); - Jrhs.applyBoundary("neumann"); - - if (jpar_bndry_width > 0) { - // Boundary in jpar - if (mesh->firstX()) { - for (int i = jpar_bndry_width; i >= 0; i--) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - Jrhs(i, j, k) = 0.5 * Jrhs(i + 1, j, k); - } - } - } - } - if (mesh->lastX()) { - for (int i = mesh->LocalNx - jpar_bndry_width - 1; i < mesh->LocalNx; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - Jrhs(i, j, k) = 0.5 * Jrhs(i - 1, j, k); - } - } + auto B0 = tokamak_options.Bxy; + + U1 += (gamma * B0 * B0) * Grad_par(Jrhs, CELL_CENTRE) + (gamma * b0xcv) * Grad(P); + + // Second matrix, solving Alfven wave dynamics + static std::unique_ptr invU{nullptr}; + if (!invU) { + invU = InvertPar::create(); } - } - } - mesh->communicate(Jrhs, ddt(P)); + invU->setCoefA(1.); + invU->setCoefB(-SQ(gamma) * B0 * B0); + ddt(U) = invU->solve(U1); + ddt(U).applyBoundary(); - Field3D U1 = ddt(U); - U1 += (gamma * B0 * B0) * Grad_par(Jrhs, CELL_CENTRE) + (gamma * b0xcv) * Grad(P); + // Third matrix, applying U + Field3D phi3 = phiSolver->solve(ddt(U)); + mesh->communicate(phi3); + phi3.applyBoundary("neumann"); + Field3D B0phi3 = B0 * phi3; + mesh->communicate(B0phi3); + ddt(Psi) = ddt(Psi) - gamma * Grad_par(B0phi3, loc) / B0; + ddt(Psi).applyBoundary(); - // Second matrix, solving Alfven wave dynamics - static std::unique_ptr invU{nullptr}; - if (!invU) { - invU = InvertPar::create(); + return 0; } - invU->setCoefA(1.); - invU->setCoefB(-SQ(gamma) * B0 * B0); - ddt(U) = invU->solve(U1); - ddt(U).applyBoundary(); - - // Third matrix, applying U - Field3D phi3 = phiSolver->solve(ddt(U)); - mesh->communicate(phi3); - phi3.applyBoundary("neumann"); - Field3D B0phi3 = B0 * phi3; - mesh->communicate(B0phi3); - ddt(Psi) = ddt(Psi) - gamma * Grad_par(B0phi3, loc) / B0; - ddt(Psi).applyBoundary(); - - return 0; - } + /******************************************************************************* + * Jacobian-vector multiply + * + * Input + * System state is in (P, Psi, U) + * Vector v is in (F_P, F_Psi, F_U) + * Output + * Jacobian-vector multiplied Jv should be in (P, Psi, U) + * + * NOTE: EXPERIMENTAL + * enable by setting solver / use_jacobian = true in BOUT.inp + *******************************************************************************/ - /******************************************************************************* - * Jacobian-vector multiply - * - * Input - * System state is in (P, Psi, U) - * Vector v is in (F_P, F_Psi, F_U) - * Output - * Jacobian-vector multiplied Jv should be in (P, Psi, U) - * - * NOTE: EXPERIMENTAL - * enable by setting solver / use_jacobian = true in BOUT.inp - *******************************************************************************/ - - int jacobian(BoutReal UNUSED(t)) { - // Communicate - mesh->communicate(ddt(P), ddt(Psi), ddt(U)); - - phi = phiSolver->solve(ddt(U)); - - Jpar = Delp2(ddt(Psi)); - - mesh->communicate(phi, Jpar); - - Field3D JP = -b0xGrad_dot_Grad(phi, P0); - JP.setBoundary("P"); - JP.applyBoundary(); - Field3D B0phi = B0 * phi; - mesh->communicate(B0phi); - Field3D JPsi = -Grad_par(B0phi, loc) / B0; - JPsi.setBoundary("Psi"); - JPsi.applyBoundary(); - - Field3D JU = b0xcv * Grad(ddt(P)) - SQ(B0) * Grad_par(Jpar, CELL_CENTRE) - + SQ(B0) * b0xGrad_dot_Grad(ddt(Psi), J0, CELL_CENTRE); - JU.setBoundary("U"); - JU.applyBoundary(); - - // Put result into time-derivatives - - ddt(P) = JP; - ddt(Psi) = JPsi; - ddt(U) = JU; - - return 0; - } + int jacobian(BoutReal UNUSED(t)) { + // Communicate + mesh->communicate(ddt(P), ddt(Psi), ddt(U)); - /******************************************************************************* - * Preconditioner for when phi solved as a constraint - * Currently only possible with the IDA solver - * - * o System state in variables (as in rhs function) - * o Values to be inverted in F_vars - * - * o Return values should be in vars (overwriting system state) - *******************************************************************************/ - - int precon_phi(BoutReal UNUSED(t), BoutReal UNUSED(cj), BoutReal UNUSED(delta)) { - ddt(phi) = phiSolver->solve(C_phi - ddt(U)); - return 0; - } + phi = phiSolver->solve(ddt(U)); + + Jpar = Delp2(ddt(Psi)); + + mesh->communicate(phi, Jpar); + + Field3D JP = -b0xGrad_dot_Grad(phi, P0); + JP.setBoundary("P"); + JP.applyBoundary(); + + auto B0 = tokamak_options.Bxy; + + Field3D B0phi = B0 * phi; + mesh->communicate(B0phi); + Field3D JPsi = -Grad_par(B0phi, loc) / B0; + JPsi.setBoundary("Psi"); + JPsi.applyBoundary(); + + Field3D JU = b0xcv * Grad(ddt(P)) - SQ(B0) * Grad_par(Jpar, CELL_CENTRE) + + SQ(B0) * b0xGrad_dot_Grad(ddt(Psi), J0, CELL_CENTRE); + JU.setBoundary("U"); + JU.applyBoundary(); + + // Put result into time-derivatives + + ddt(P) = JP; + ddt(Psi) = JPsi; + ddt(U) = JU; + + return 0; + } + + /******************************************************************************* + * Preconditioner for when phi solved as a constraint + * Currently only possible with the IDA solver + * + * o System state in variables (as in rhs function) + * o Values to be inverted in F_vars + * + * o Return values should be in vars (overwriting system state) + *******************************************************************************/ + + int precon_phi(BoutReal UNUSED(t), BoutReal UNUSED(cj), BoutReal UNUSED(delta)) { + ddt(phi) = phiSolver->solve(C_phi - ddt(U)); + return 0; + } }; BOUTMAIN(ELMpb); diff --git a/examples/elm-pb/elm_pb.cxx b/examples/elm-pb/elm_pb.cxx index e7070d2b02..b8a8bd41b9 100644 --- a/examples/elm-pb/elm_pb.cxx +++ b/examples/elm-pb/elm_pb.cxx @@ -5,7 +5,6 @@ * Can also include the Vpar compressional term *******************************************************************************/ -#include #include #include #include @@ -15,6 +14,7 @@ #include #include #include +#include #include #include @@ -36,1892 +36,1860 @@ BOUT_OVERRIDE_DEFAULT_OPTION("phi:bndry_xout", "none"); /// 3-field ELM simulation class ELMpb : public PhysicsModel { private: - // 2D inital profiles - Field2D J0, P0; // Current and pressure - Vector2D b0xcv; // Curvature term - Field2D beta; // Used for Vpar terms - Coordinates::FieldMetric gradparB; - Field2D phi0; // When diamagnetic terms used - Field2D Psixy, x; - Coordinates::FieldMetric U0; // 0th vorticity of equilibrium flow, - // radial flux coordinate, normalized radial flux coordinate - - bool constn0; - // the total height, average width and center of profile of N0 - BoutReal n0_height, n0_ave, n0_width, n0_center, n0_bottom_x, Nbar, Tibar, Tebar; - - BoutReal Tconst; // the ampitude of constant temperature - - Field2D N0, Ti0, Te0, Ne0; // number density and temperature - Field2D Pi0, Pe0; - Field2D q95; - Field3D ubyn; - bool n0_fake_prof, T0_fake_prof; - - // B field vectors - Vector2D B0vec; // B0 field vector - - // V0 field vectors - Vector2D V0net; // net flow - - // 3D evolving variables - Field3D U, Psi, P, Vpar, Psi_loc; - - // Derived 3D variables - Field3D Jpar, phi; // Parallel current, electric potential - - Field3D Jpar2; // Delp2 of Parallel current - - Field3D tmpP2; // Grad2_par2new of pressure - Field3D tmpU2; // Grad2_par2new of Parallel vorticity - Field3D tmpA2; // Grad2_par2new of Parallel vector potential - - // Constraint - Field3D C_phi; - - // Parameters - BoutReal density; // Number density [m^-3] - BoutReal Bbar, Lbar, Tbar, Va; // Normalisation constants - BoutReal dnorm; // For diamagnetic terms: 1 / (2. * wci * Tbar) - BoutReal dia_fact; // Multiply diamagnetic term by this - BoutReal delta_i; // Normalized ion skin depth - BoutReal omega_i; // ion gyrofrequency - - BoutReal diffusion_p4; // xqx: parallel hyper-viscous diffusion for pressure - BoutReal diffusion_u4; // xqx: parallel hyper-viscous diffusion for vorticity - BoutReal diffusion_a4; // xqx: parallel hyper-viscous diffusion for vector potential - - BoutReal diffusion_par; // Parallel pressure diffusion - BoutReal heating_P; // heating power in pressure - BoutReal hp_width; // heating profile radial width in pressure - BoutReal hp_length; // heating radial domain in pressure - BoutReal sink_P; // sink in pressure - BoutReal sp_width; // sink profile radial width in pressure - BoutReal sp_length; // sink radial domain in pressure - - BoutReal sink_Ul; // left edge sink in vorticity - BoutReal su_widthl; // left edge sink profile radial width in vorticity - BoutReal su_lengthl; // left edge sink radial domain in vorticity - - BoutReal sink_Ur; // right edge sink in vorticity - BoutReal su_widthr; // right edge sink profile radial width in vorticity - BoutReal su_lengthr; // right edge sink radial domain in vorticity - - BoutReal viscos_par; // Parallel viscosity - BoutReal viscos_perp; // Perpendicular viscosity - BoutReal hyperviscos; // Hyper-viscosity (radial) - Field3D hyper_mu_x; // Hyper-viscosity coefficient - - Field3D Dperp2Phi0, Dperp2Phi, GradPhi02, - GradPhi2; // Temporary variables for gyroviscous - Field3D GradparPhi02, GradparPhi2, GradcPhi, GradcparPhi; - Field3D Dperp2Pi0, Dperp2Pi, bracketPhi0P, bracketPhiP0, bracketPhiP; - BoutReal Upara2; - - // options - bool include_curvature, include_jpar0, compress; - bool evolve_pressure, gyroviscous; - - BoutReal vacuum_pressure; - BoutReal vacuum_trans; // Transition width - Field3D vac_mask; - - bool nonlinear; - bool evolve_jpar; - BoutReal g; // Only if compressible - bool phi_curv; - - // Poisson brackets: b0 x Grad(f) dot Grad(g) / B = [f, g] - // Method to use: BRACKET_ARAKAWA, BRACKET_STD or BRACKET_SIMPLE - /* - * Bracket method - * - * BRACKET_STD - Same as b0xGrad_dot_Grad, methods in BOUT.inp - * BRACKET_SIMPLE - Subset of terms, used in BOUT-06 - * BRACKET_ARAKAWA - Arakawa central differencing (2nd order) - * BRACKET_CTU - 1st order upwind method - * - */ - - // Bracket method for advection terms - BRACKET_METHOD bm_exb; - BRACKET_METHOD bm_mag; - int bm_exb_flag; - int bm_mag_flag; - /* BRACKET_METHOD bm_ExB = BRACKET_STD; - BRACKET_METHOD bm_mflutter = BRACKET_STD; */ - - bool diamag; - bool diamag_grad_t; // Grad_par(Te) term in Psi equation - bool diamag_phi0; // Include the diamagnetic equilibrium phi0 - - bool eHall; - BoutReal AA; // ion mass in units of the proton mass; AA=Mi/Mp - - // net flow, Er=-R*Bp*Dphi0,Dphi0=-D_min-0.5*D_0*(1.0-tanh(D_s*(x-x0))) - Field2D V0; // net flow amplitude - Field2D Dphi0; // differential potential to flux - BoutReal D_0; // potential amplitude - BoutReal D_s; // shear parameter - BoutReal x0; // velocity peak location - BoutReal sign; // direction of flow - BoutReal Psiaxis, Psibndry; - bool withflow; - bool K_H_term; // Kelvin-Holmhotz term - Field2D perp; // for test - BoutReal D_min; // constant in flow - - // for C_mod - bool experiment_Er; // read in total Er from experiment - - bool nogradparj; - bool filter_z; - int filter_z_mode; - int low_pass_z; - bool zonal_flow; - bool zonal_field; - bool zonal_bkgd; - - bool split_n0; // Solve the n=0 component of potential + // 2D inital profiles + Field2D J0, P0; // Current and pressure + Vector2D b0xcv; // Curvature term + Field2D beta; // Used for Vpar terms + Coordinates::FieldMetric gradparB; + Field2D phi0; // When diamagnetic terms used + Field2D Psixy, x; + Coordinates::FieldMetric U0; // 0th vorticity of equilibrium flow, + // radial flux coordinate, normalized radial flux coordinate + + bool constn0; + // the total height, average width and center of profile of N0 + BoutReal n0_height, n0_ave, n0_width, n0_center, n0_bottom_x, Nbar, Tibar, Tebar; + + BoutReal Tconst; // the ampitude of constant temperature + + Field2D N0, Ti0, Te0, Ne0; // number density and temperature + Field2D Pi0, Pe0; + Field2D q95; + Field3D ubyn; + bool n0_fake_prof, T0_fake_prof; + + // B field vectors + Vector2D B0vec; // B0 field vector + + // V0 field vectors + Vector2D V0net; // net flow + + // 3D evolving variables + Field3D U, Psi, P, Vpar, Psi_loc; + + // Derived 3D variables + Field3D Jpar, phi; // Parallel current, electric potential + + Field3D Jpar2; // Delp2 of Parallel current + + Field3D tmpP2; // Grad2_par2new of pressure + Field3D tmpU2; // Grad2_par2new of Parallel vorticity + Field3D tmpA2; // Grad2_par2new of Parallel vector potential + + // Constraint + Field3D C_phi; + + // Parameters + BoutReal density; // Number density [m^-3] + BoutReal Bbar, Lbar, Tbar, Va; // Normalisation constants + BoutReal dnorm; // For diamagnetic terms: 1 / (2. * wci * Tbar) + BoutReal dia_fact; // Multiply diamagnetic term by this + BoutReal delta_i; // Normalized ion skin depth + BoutReal omega_i; // ion gyrofrequency + + BoutReal diffusion_p4; // xqx: parallel hyper-viscous diffusion for pressure + BoutReal diffusion_u4; // xqx: parallel hyper-viscous diffusion for vorticity + BoutReal diffusion_a4; // xqx: parallel hyper-viscous diffusion for vector potential + + BoutReal diffusion_par; // Parallel pressure diffusion + BoutReal heating_P; // heating power in pressure + BoutReal hp_width; // heating profile radial width in pressure + BoutReal hp_length; // heating radial domain in pressure + BoutReal sink_P; // sink in pressure + BoutReal sp_width; // sink profile radial width in pressure + BoutReal sp_length; // sink radial domain in pressure + + BoutReal sink_Ul; // left edge sink in vorticity + BoutReal su_widthl; // left edge sink profile radial width in vorticity + BoutReal su_lengthl; // left edge sink radial domain in vorticity + + BoutReal sink_Ur; // right edge sink in vorticity + BoutReal su_widthr; // right edge sink profile radial width in vorticity + BoutReal su_lengthr; // right edge sink radial domain in vorticity + + BoutReal viscos_par; // Parallel viscosity + BoutReal viscos_perp; // Perpendicular viscosity + BoutReal hyperviscos; // Hyper-viscosity (radial) + Field3D hyper_mu_x; // Hyper-viscosity coefficient + + Field3D Dperp2Phi0, Dperp2Phi, GradPhi02, + GradPhi2; // Temporary variables for gyroviscous + Field3D GradparPhi02, GradparPhi2, GradcPhi, GradcparPhi; + Field3D Dperp2Pi0, Dperp2Pi, bracketPhi0P, bracketPhiP0, bracketPhiP; + BoutReal Upara2; + + // options + bool include_curvature, include_jpar0, compress; + bool evolve_pressure, gyroviscous; + + BoutReal vacuum_pressure; + BoutReal vacuum_trans; // Transition width + Field3D vac_mask; + + bool nonlinear; + bool evolve_jpar; + BoutReal g; // Only if compressible + bool phi_curv; + + // Poisson brackets: b0 x Grad(f) dot Grad(g) / B = [f, g] + // Method to use: BRACKET_ARAKAWA, BRACKET_STD or BRACKET_SIMPLE + /* + * Bracket method + * + * BRACKET_STD - Same as b0xGrad_dot_Grad, methods in BOUT.inp + * BRACKET_SIMPLE - Subset of terms, used in BOUT-06 + * BRACKET_ARAKAWA - Arakawa central differencing (2nd order) + * BRACKET_CTU - 1st order upwind method + * + */ + + // Bracket method for advection terms + BRACKET_METHOD bm_exb; + BRACKET_METHOD bm_mag; + int bm_exb_flag; + int bm_mag_flag; + /* BRACKET_METHOD bm_ExB = BRACKET_STD; + BRACKET_METHOD bm_mflutter = BRACKET_STD; */ + + bool diamag; + bool diamag_grad_t; // Grad_par(Te) term in Psi equation + bool diamag_phi0; // Include the diamagnetic equilibrium phi0 + + bool eHall; + BoutReal AA; // ion mass in units of the proton mass; AA=Mi/Mp + + // net flow, Er=-R*Bp*Dphi0,Dphi0=-D_min-0.5*D_0*(1.0-tanh(D_s*(x-x0))) + Field2D V0; // net flow amplitude + Field2D Dphi0; // differential potential to flux + BoutReal D_0; // potential amplitude + BoutReal D_s; // shear parameter + BoutReal x0; // velocity peak location + BoutReal sign; // direction of flow + BoutReal Psiaxis, Psibndry; + bool withflow; + bool K_H_term; // Kelvin-Holmhotz term + Field2D perp; // for test + BoutReal D_min; // constant in flow + + // for C_mod + bool experiment_Er; // read in total Er from experiment + + bool nogradparj; + bool filter_z; + int filter_z_mode; + int low_pass_z; + bool zonal_flow; + bool zonal_field; + bool zonal_bkgd; + + bool split_n0; // Solve the n=0 component of potential #if BOUT_HAS_HYPRE - std::unique_ptr laplacexy{nullptr}; // Laplacian solver in X-Y (n=0) + std::unique_ptr laplacexy{nullptr}; // Laplacian solver in X-Y (n=0) #else - std::unique_ptr laplacexy{nullptr}; // Laplacian solver in X-Y (n=0) + std::unique_ptr laplacexy{nullptr}; // Laplacian solver in X-Y (n=0) #endif - Field2D phi2D; // Axisymmetric phi - - bool relax_j_vac; - BoutReal relax_j_tconst; // Time-constant for j relax - - bool smooth_j_x; // Smooth Jpar in the x direction - - int jpar_bndry_width; // Zero jpar in a boundary region - - bool sheath_boundaries; // Apply sheath boundaries in Y - - bool parallel_lr_diff; // Use left and right shifted stencils for parallel differences - - bool phi_constraint; // Solver for phi using a solver constraint - - bool include_rmp; // Include RMP coil perturbation - bool simple_rmp; // Just use a simple form for the perturbation - - BoutReal rmp_factor; // Multiply amplitude by this factor - BoutReal rmp_ramp; // Ramp-up time for RMP [s]. negative -> instant - BoutReal rmp_freq; // Amplitude oscillation frequency [Hz] (negative -> no oscillation) - BoutReal rmp_rotate; // Rotation rate [Hz] - bool rmp_vac_mask; - Field3D rmp_Psi0; // Parallel vector potential from Resonant Magnetic Perturbation (RMP) - // coils - Field3D rmp_Psi; // Value used in calculations - Field3D rmp_dApdt; // Time variation - - BoutReal vac_lund, core_lund; // Lundquist number S = (Tau_R / Tau_A). -ve -> infty - BoutReal vac_resist, core_resist; // The resistivities (just 1 / S) - Field3D eta; // Resistivity profile (1 / S) - bool spitzer_resist; // Use Spitzer formula for resistivity - BoutReal Zeff; // Z effective for resistivity formula - - BoutReal hyperresist; // Hyper-resistivity coefficient (in core only) - BoutReal ehyperviscos; // electron Hyper-viscosity coefficient - - int damp_width; // Width of inner damped region - BoutReal damp_t_const; // Timescale of damping - - // Metric coefficients - Field2D Rxy, Bpxy, Btxy, B0, hthe; - Field2D I; // Shear factor - - const BoutReal MU0 = 4.0e-7 * PI; - const BoutReal Mi = 2.0 * 1.6726e-27; // Ion mass - const BoutReal Me = 9.1094e-31; // Electron mass - const BoutReal mi_me = Mi / Me; - - // Communication objects - FieldGroup comms; - - /// Solver for inverting Laplacian - std::unique_ptr phiSolver{nullptr}; - std::unique_ptr aparSolver{nullptr}; - - const Field2D N0tanh(BoutReal n0_height, BoutReal n0_ave, BoutReal n0_width, - BoutReal n0_center, BoutReal n0_bottom_x) { - Field2D result; - result.allocate(); - - BoutReal Grid_NX, Grid_NXlimit; // the grid number on x, and the - BoutReal Jysep; - mesh->get(Grid_NX, "nx"); - mesh->get(Jysep, "jyseps1_1"); - Grid_NXlimit = n0_bottom_x * Grid_NX; - output.write("Jysep1_1 = {:d} Grid number = {:e}\n", int(Jysep), Grid_NX); - - if (Jysep > 0.) { // for single null geometry - BoutReal Jxsep, Jysep2; - mesh->get(Jxsep, "ixseps1"); - mesh->get(Jysep2, "jyseps2_2"); - - for (auto i : result) { - BoutReal mgx = mesh->GlobalX(i.x()); - BoutReal xgrid_num = (Jxsep + 1.) / Grid_NX; - - int globaly = mesh->getGlobalYIndex(i.y()); - - if (mgx > xgrid_num || (globaly <= int(Jysep) - 2) - || (globaly > int(Jysep2) + 2)) { - mgx = xgrid_num; - } - BoutReal rlx = mgx - n0_center; - BoutReal temp = exp(rlx / n0_width); - BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); - result[i] = 0.5 * (1.0 - dampr) * n0_height + n0_ave; - } - } else { // circular geometry - for (auto i : result) { - BoutReal mgx = mesh->GlobalX(i.x()); - BoutReal xgrid_num = Grid_NXlimit / Grid_NX; - if (mgx > xgrid_num) { - mgx = xgrid_num; - } - BoutReal rlx = mgx - n0_center; - BoutReal temp = exp(rlx / n0_width); - BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); - result[i] = 0.5 * (1.0 - dampr) * n0_height + n0_ave; - } - } + Field2D phi2D; // Axisymmetric phi - mesh->communicate(result); + bool relax_j_vac; + BoutReal relax_j_tconst; // Time-constant for j relax - return result; - } + bool smooth_j_x; // Smooth Jpar in the x direction -protected: - int init(bool restarting) override { - bool noshear; + int jpar_bndry_width; // Zero jpar in a boundary region - Coordinates* metric = mesh->getCoordinates(); + bool sheath_boundaries; // Apply sheath boundaries in Y - output.write("Solving high-beta flute reduced equations\n"); - output.write("\tFile : {:s}\n", __FILE__); - output.write("\tCompiled: {:s} at {:s}\n", __DATE__, __TIME__); + bool parallel_lr_diff; // Use left and right shifted stencils for parallel differences - ////////////////////////////////////////////////////////////// - // Load data from the grid + bool phi_constraint; // Solver for phi using a solver constraint - // Load 2D profiles - mesh->get(J0, "Jpar0"); // A / m^2 - mesh->get(P0, "pressure"); // Pascals + bool include_rmp; // Include RMP coil perturbation + bool simple_rmp; // Just use a simple form for the perturbation - // Load curvature term - b0xcv.covariant = false; // Read contravariant components - mesh->get(b0xcv, "bxcv"); // mixed units x: T y: m^-2 z: m^-2 + BoutReal rmp_factor; // Multiply amplitude by this factor + BoutReal rmp_ramp; // Ramp-up time for RMP [s]. negative -> instant + BoutReal rmp_freq; // Amplitude oscillation frequency [Hz] (negative -> no oscillation) + BoutReal rmp_rotate; // Rotation rate [Hz] + bool rmp_vac_mask; + Field3D rmp_Psi0; // Parallel vector potential from Resonant Magnetic Perturbation (RMP) + // coils + Field3D rmp_Psi; // Value used in calculations + Field3D rmp_dApdt; // Time variation - // Load metrics - if (mesh->get(Rxy, "Rxy")) { // m - throw BoutException("Error: Cannot read Rxy from grid\n"); - } - if (mesh->get(Bpxy, "Bpxy")) { // T - throw BoutException("Error: Cannot read Bpxy from grid\n"); - } - mesh->get(Btxy, "Btxy"); // T - mesh->get(B0, "Bxy"); // T - mesh->get(hthe, "hthe"); // m - mesh->get(I, "sinty"); // m^-2 T^-1 - mesh->get(Psixy, "psixy"); // get Psi - mesh->get(Psiaxis, "psi_axis"); // axis flux - mesh->get(Psibndry, "psi_bndry"); // edge flux - - // Set locations of staggered variables - // Note, use of staggered grids in elm-pb is untested and may not be completely - // implemented. Parallel boundary conditions are especially likely to be wrong. - if (mesh->StaggerGrids) { - loc = CELL_YLOW; - } else { - loc = CELL_CENTRE; - } - Jpar.setLocation(loc); - Vpar.setLocation(loc); - Psi.setLocation(loc); - eta.setLocation(loc); - Psi_loc.setBoundary("Psi_loc"); - - ////////////////////////////////////////////////////////////// - auto& globalOptions = Options::root(); - auto& options = globalOptions["highbeta"]; - - constn0 = options["constn0"].withDefault(true); - // use the hyperbolic profile of n0. If both n0_fake_prof and - // T0_fake_prof are false, use the profiles from grid file - n0_fake_prof = options["n0_fake_prof"].withDefault(false); - // the total height of profile of N0, in percentage of Ni_x - n0_height = options["n0_height"].withDefault(0.4); - // the center or average of N0, in percentage of Ni_x - n0_ave = options["n0_ave"].withDefault(0.01); - // the width of the gradient of N0,in percentage of x - n0_width = options["n0_width"].withDefault(0.1); - // the grid number of the center of N0, in percentage of x - n0_center = options["n0_center"].withDefault(0.633); - // the start of flat region of N0 on SOL side, in percentage of x - n0_bottom_x = options["n0_bottom_x"].withDefault(0.81); - T0_fake_prof = options["T0_fake_prof"].withDefault(false); - // the amplitude of constant temperature, in percentage - Tconst = options["Tconst"].withDefault(-1.0); - - density = options["density"].doc("Number density [m^-3]").withDefault(1.0e19); - - evolve_jpar = options["evolve_jpar"] - .doc("If true, evolve J rather than Psi") - .withDefault(false); - phi_constraint = options["phi_constraint"] - .doc("Use solver constraint for phi?") - .withDefault(false); - - // Effects to include/exclude - include_curvature = options["include_curvature"].withDefault(true); - include_jpar0 = options["include_jpar0"].withDefault(true); - evolve_pressure = options["evolve_pressure"].withDefault(true); - nogradparj = options["nogradparj"].withDefault(false); - - compress = options["compress"] - .doc("Include compressibility effects (evolve Vpar)?") - .withDefault(false); - gyroviscous = options["gyroviscous"].withDefault(false); - nonlinear = options["nonlinear"].doc("Include nonlinear terms?").withDefault(false); - - // option for ExB Poisson Bracket - bm_exb_flag = options["bm_exb_flag"] - .doc("ExB Poisson bracket method. 0=standard;1=simple;2=arakawa") - .withDefault(0); - switch (bm_exb_flag) { - case 0: { - bm_exb = BRACKET_STD; - output << "\tBrackets for ExB: default differencing\n"; - break; - } - case 1: { - bm_exb = BRACKET_SIMPLE; - output << "\tBrackets for ExB: simplified operator\n"; - break; - } - case 2: { - bm_exb = BRACKET_ARAKAWA; - output << "\tBrackets for ExB: Arakawa scheme\n"; - break; - } - case 3: { - bm_exb = BRACKET_CTU; - output << "\tBrackets for ExB: Corner Transport Upwind method\n"; - break; - } - default: - throw BoutException("Invalid choice of bracket method. Must be 0 - 3\n"); - } + BoutReal vac_lund, core_lund; // Lundquist number S = (Tau_R / Tau_A). -ve -> infty + BoutReal vac_resist, core_resist; // The resistivities (just 1 / S) + Field3D eta; // Resistivity profile (1 / S) + bool spitzer_resist; // Use Spitzer formula for resistivity + BoutReal Zeff; // Z effective for resistivity formula - bm_mag_flag = - options["bm_mag_flag"].doc("magnetic flutter Poisson Bracket").withDefault(0); - switch (bm_mag_flag) { - case 0: { - bm_mag = BRACKET_STD; - output << "\tBrackets: default differencing\n"; - break; - } - case 1: { - bm_mag = BRACKET_SIMPLE; - output << "\tBrackets: simplified operator\n"; - break; - } - case 2: { - bm_mag = BRACKET_ARAKAWA; - output << "\tBrackets: Arakawa scheme\n"; - break; - } - case 3: { - bm_mag = BRACKET_CTU; - output << "\tBrackets: Corner Transport Upwind method\n"; - break; - } - default: - throw BoutException("Invalid choice of bracket method. Must be 0 - 3\n"); - } + BoutReal hyperresist; // Hyper-resistivity coefficient (in core only) + BoutReal ehyperviscos; // electron Hyper-viscosity coefficient - eHall = options["eHall"] - .doc("electron Hall or electron parallel pressue gradient effects?") - .withDefault(false); - AA = options["AA"].doc("ion mass in units of proton mass").withDefault(1.0); - - diamag = options["diamag"].doc("Diamagnetic effects?").withDefault(false); - diamag_grad_t = options["diamag_grad_t"] - .doc("Grad_par(Te) term in Psi equation") - .withDefault(diamag); - diamag_phi0 = - options["diamag_phi0"].doc("Include equilibrium phi0").withDefault(diamag); - dia_fact = options["dia_fact"] - .doc("Scale diamagnetic effects by this factor") - .withDefault(1.0); - - // withflow or not - withflow = options["withflow"].withDefault(false); - // keep K-H term - K_H_term = options["K_H_term"].withDefault(true); - // velocity magnitude - D_0 = options["D_0"].withDefault(0.0); - // flowshear - D_s = options["D_s"].withDefault(0.0); - // flow location - x0 = options["x0"].withDefault(0.0); - // flow direction, -1 means negative electric field - sign = options["sign"].withDefault(1.0); - // a constant - D_min = options["D_min"].withDefault(3000.0); - - experiment_Er = options["experiment_Er"].withDefault(false); - - noshear = options["noshear"].withDefault(false); - - relax_j_vac = options["relax_j_vac"] - .doc("Relax vacuum current to zero") - .withDefault(false); - relax_j_tconst = options["relax_j_tconst"] - .doc("Time constant for relaxation of vacuum current. Alfven " - "(normalised) units") - .withDefault(0.1); - - // Toroidal filtering - filter_z = options["filter_z"] - .doc("Filter a single toroidal mode number? The mode to keep is " - "filter_z_mode.") - .withDefault(false); - filter_z_mode = options["filter_z_mode"] - .doc("Single toroidal mode number to keep") - .withDefault(1); - low_pass_z = options["low_pass_z"].doc("Low-pass filter").withDefault(-1); - zonal_flow = options["zonal_flow"] - .doc("Keep zonal (n=0) component of potential?") - .withDefault(false); - zonal_field = options["zonal_field"] - .doc("Keep zonal (n=0) component of magnetic potential?") - .withDefault(false); - zonal_bkgd = options["zonal_bkgd"] - .doc("Evolve zonal (n=0) pressure profile?") - .withDefault(false); - - // n = 0 electrostatic potential solve - split_n0 = options["split_n0"] - .doc("Solve zonal (n=0) component of potential using LaplaceXY?") - .withDefault(false); - if (split_n0) { - // Create an XY solver for n=0 component -#if BOUT_HAS_HYPRE - laplacexy = bout::utils::make_unique(mesh); -#else - laplacexy = bout::utils::make_unique(mesh); -#endif - // Set coefficients for Boussinesq solve - laplacexy->setCoefs(1.0, 0.0); - phi2D = 0.0; // Starting guess - phi2D.setBoundary("phi"); - } + int damp_width; // Width of inner damped region + BoutReal damp_t_const; // Timescale of damping - // Radial smoothing - smooth_j_x = options["smooth_j_x"].doc("Smooth Jpar in x").withDefault(false); - - // Jpar boundary region - jpar_bndry_width = options["jpar_bndry_width"] - .doc("Number of cells near the boundary where jpar = 0") - .withDefault(-1); - - sheath_boundaries = options["sheath_boundaries"] - .doc("Apply sheath boundaries in Y?") - .withDefault(false); - - // Parallel differencing - parallel_lr_diff = - options["parallel_lr_diff"] - .doc("Use left and right shifted stencils for parallel differences?") - .withDefault(false); - - // RMP-related options - include_rmp = options["include_rmp"] - .doc("Read RMP field rmp_A from grid?") - .withDefault(false); - - simple_rmp = - options["simple_rmp"].doc("Include a simple RMP model?").withDefault(false); - rmp_factor = options["rmp_factor"].withDefault(1.0); - rmp_ramp = options["rmp_ramp"].withDefault(-1.0); - rmp_freq = options["rmp_freq"].withDefault(-1.0); - rmp_rotate = options["rmp_rotate"].withDefault(0.0); - - // Vacuum region control - vacuum_pressure = - options["vacuum_pressure"] - .doc("Fraction of peak pressure, below which is considered vacuum.") - .withDefault(0.02); - vacuum_trans = - options["vacuum_trans"] - .doc("Vacuum boundary transition width, as fraction of peak pressure.") - .withDefault(0.005); - - // Resistivity and hyper-resistivity options - vac_lund = - options["vac_lund"].doc("Lundquist number in vacuum region").withDefault(0.0); - core_lund = - options["core_lund"].doc("Lundquist number in core region").withDefault(0.0); - hyperresist = options["hyperresist"].withDefault(-1.0); - ehyperviscos = options["ehyperviscos"].withDefault(-1.0); - spitzer_resist = options["spitzer_resist"] - .doc("Use Spitzer resistivity?") - .withDefault(false); - Zeff = options["Zeff"].withDefault(2.0); // Z effective - - // Inner boundary damping - damp_width = options["damp_width"] - .doc("Width of the radial damping regions, in grid cells") - .withDefault(0); - damp_t_const = - options["damp_t_const"] - .doc("Time constant for damping in radial regions. Normalised time units.") - .withDefault(0.1); - - // Viscosity and hyper-viscosity - viscos_par = options["viscos_par"].doc("Parallel viscosity").withDefault(-1.0); - viscos_perp = options["viscos_perp"].doc("Perpendicular viscosity").withDefault(-1.0); - hyperviscos = options["hyperviscos"].doc("Radial hyperviscosity").withDefault(-1.0); - - diffusion_par = - options["diffusion_par"].doc("Parallel pressure diffusion").withDefault(-1.0); - diffusion_p4 = options["diffusion_p4"] - .doc("parallel hyper-viscous diffusion for pressure") - .withDefault(-1.0); - diffusion_u4 = options["diffusion_u4"] - .doc("parallel hyper-viscous diffusion for vorticity") - .withDefault(-1.0); - diffusion_a4 = options["diffusion_a4"] - .doc("parallel hyper-viscous diffusion for vector potential") - .withDefault(-1.0); - - // heating factor in pressure - // heating power in pressure - heating_P = options["heating_P"].withDefault(-1.0); - // the percentage of radial grid points for heating profile radial - // width in pressure - hp_width = options["hp_width"].withDefault(0.1); - // the percentage of radial grid points for heating profile radial - // domain in pressure - hp_length = options["hp_length"].withDefault(0.04); - - // sink factor in pressure - // sink in pressure - sink_P = options["sink_P"].withDefault(-1.0); - // the percentage of radial grid points for sink profile radial - // width in pressure - sp_width = options["sp_width"].withDefault(0.05); - // the percentage of radial grid points for sink profile radial - // domain in pressure - sp_length = options["sp_length"].withDefault(0.04); - - // left edge sink factor in vorticity - // left edge sink in vorticity - sink_Ul = options["sink_Ul"].withDefault(-1.0); - // the percentage of left edge radial grid points for sink profile - // radial width in vorticity - su_widthl = options["su_widthl"].withDefault(0.06); - // the percentage of left edge radial grid points for sink profile - // radial domain in vorticity - su_lengthl = options["su_lengthl"].withDefault(0.15); - - // right edge sink factor in vorticity - // right edge sink in vorticity - sink_Ur = options["sink_Ur"].withDefault(-1.0); - // the percentage of right edge radial grid points for sink profile - // radial width in vorticity - su_widthr = options["su_widthr"].withDefault(0.06); - // the percentage of right edge radial grid points for sink profile - // radial domain in vorticity - su_lengthr = options["su_lengthr"].withDefault(0.15); - - // Compressional terms - phi_curv = - options["phi_curv"].doc("ExB compression in P equation?").withDefault(true); - g = options["gamma"].doc("Ratio of specific heats").withDefault(5.0 / 3.0); - - x = (Psixy - Psiaxis) / (Psibndry - Psiaxis); - - if (experiment_Er) { // get er from experiment - mesh->get(Dphi0, "Epsi"); - diamag_phi0 = false; - K_H_term = false; - } else { - Dphi0 = -D_min - 0.5 * D_0 * (1.0 - tanh(D_s * (x - x0))); - } + bout::TokamakOptions tokamak_options = bout::TokamakOptions(*mesh); - if (sign < 0) { // change flow direction - Dphi0 *= -1; - } + const BoutReal MU0 = 4.0e-7 * PI; + const BoutReal Mi = 2.0 * 1.6726e-27; // Ion mass + const BoutReal Me = 9.1094e-31; // Electron mass + const BoutReal mi_me = Mi / Me; - V0 = -Rxy * Bpxy * Dphi0 / B0; + // Communication objects + FieldGroup comms; - if (simple_rmp) { - include_rmp = true; - } + /// Solver for inverting Laplacian + std::unique_ptr phiSolver{nullptr}; + std::unique_ptr aparSolver{nullptr}; - // toroidal and poloidal mode numbers - const int rmp_n = - options["rmp_n"].doc("Simple RMP toroidal mode number").withDefault(3); - const int rmp_m = - options["rmp_m"].doc("Simple RMP poloidal mode number").withDefault(9); - const int rmp_polwid = options["rmp_polwid"] - .doc("Poloidal width (-ve -> full, fraction of 2pi)") - .withDefault(-1.0); - const int rmp_polpeak = options["rmp_polpeak"] - .doc("Peak poloidal location (fraction of 2pi)") - .withDefault(0.5); - rmp_vac_mask = - options["rmp_vac_mask"].doc("Should a vacuum mask be applied?").withDefault(true); - // Divide n by the size of the domain - const int zperiod = globalOptions["zperiod"].withDefault(1); - - if (include_rmp) { - // Including external field coils. - if (simple_rmp) { - // Use a fairly simple form for the perturbation - Field2D pol_angle; - if (mesh->get(pol_angle, "pol_angle")) { - output_warn.write(" ***WARNING: need poloidal angle for simple RMP\n"); - include_rmp = false; - } else { - if ((rmp_n % zperiod) != 0) { - output_warn.write( - " ***WARNING: rmp_n ({:d}) not a multiple of zperiod ({:d})\n", rmp_n, - zperiod); - } - - output.write("\tMagnetic perturbation: n = {:d}, m = {:d}, magnitude {:e} Tm\n", - rmp_n, rmp_m, rmp_factor); - - rmp_Psi0 = 0.0; - if (mesh->lastX()) { - // Set the outer boundary - for (int jx = mesh->LocalNx - 4; jx < mesh->LocalNx; jx++) { - for (int jy = 0; jy < mesh->LocalNy; jy++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { + const Field2D N0tanh(BoutReal n0_height, BoutReal n0_ave, BoutReal n0_width, + BoutReal n0_center, BoutReal n0_bottom_x) { + Field2D result; + result.allocate(); + + BoutReal Grid_NX, Grid_NXlimit; // the grid number on x, and the + BoutReal Jysep; + mesh->get(Grid_NX, "nx"); + mesh->get(Jysep, "jyseps1_1"); + Grid_NXlimit = n0_bottom_x * Grid_NX; + output.write("Jysep1_1 = {:d} Grid number = {:e}\n", int(Jysep), Grid_NX); - BoutReal angle = - rmp_m * pol_angle(jx, jy) - + rmp_n * ((BoutReal)jz) * mesh->getCoordinates()->dz(jx, jy, jz); - rmp_Psi0(jx, jy, jz) = - (((BoutReal)(jx - 4)) / ((BoutReal)(mesh->LocalNx - 5))) - * rmp_factor * cos(angle); - if (rmp_polwid > 0.0) { - // Multiply by a Gaussian in poloidal angle - BoutReal gx = - ((pol_angle(jx, jy) / (2. * PI)) - rmp_polpeak) / rmp_polwid; - rmp_Psi0(jx, jy, jz) *= exp(-gx * gx); - } + if (Jysep > 0.) { // for single null geometry + BoutReal Jxsep, Jysep2; + mesh->get(Jxsep, "ixseps1"); + mesh->get(Jysep2, "jyseps2_2"); + + for (auto i: result) { + BoutReal mgx = mesh->GlobalX(i.x()); + BoutReal xgrid_num = (Jxsep + 1.) / Grid_NX; + + int globaly = mesh->getGlobalYIndex(i.y()); + + if (mgx > xgrid_num || (globaly <= int(Jysep) - 2) + || (globaly > int(Jysep2) + 2)) { + mgx = xgrid_num; } - } + BoutReal rlx = mgx - n0_center; + BoutReal temp = exp(rlx / n0_width); + BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); + result[i] = 0.5 * (1.0 - dampr) * n0_height + n0_ave; } - } - - // Now have a simple model for Psi due to coils at the outer boundary - // Need to calculate Psi inside the domain, enforcing j = 0 - - Jpar = 0.0; - auto psiLap = std::unique_ptr{Laplacian::create(nullptr, loc)}; - psiLap->setInnerBoundaryFlags(INVERT_AC_GRAD); // Zero gradient inner BC - psiLap->setOuterBoundaryFlags(INVERT_SET); // Set to rmp_Psi0 on outer boundary - rmp_Psi0 = psiLap->solve(Jpar, rmp_Psi0); - mesh->communicate(rmp_Psi0); - } - } else { - // Load perturbation from grid file. - include_rmp = !mesh->get(rmp_Psi0, "rmp_A"); // Only include if found - if (!include_rmp) { - output_warn.write("WARNING: Couldn't read 'rmp_A' from grid file\n"); - } - // Multiply by factor - rmp_Psi0 *= rmp_factor; - } - } + } else { // circular geometry + for (auto i: result) { + BoutReal mgx = mesh->GlobalX(i.x()); + BoutReal xgrid_num = Grid_NXlimit / Grid_NX; + if (mgx > xgrid_num) { + mgx = xgrid_num; + } + BoutReal rlx = mgx - n0_center; + BoutReal temp = exp(rlx / n0_width); + BoutReal dampr = ((temp - 1.0 / temp) / (temp + 1.0 / temp)); + result[i] = 0.5 * (1.0 - dampr) * n0_height + n0_ave; + } + } - if (!include_curvature) { - b0xcv = 0.0; - } + mesh->communicate(result); - if (!include_jpar0) { - J0 = 0.0; + return result; } - if (noshear) { - if (include_curvature) { - b0xcv.z += I * b0xcv.x; - } - I = 0.0; - } +protected: + int init(bool restarting) override { - ////////////////////////////////////////////////////////////// - // SHIFTED RADIAL COORDINATES + bool noshear; - if (mesh->IncIntShear) { - // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - metric->setIntShiftTorsion(I); + Coordinates *metric = mesh->getCoordinates(); - } else { - // Dimits style, using local coordinate system - if (include_curvature) { - b0xcv.z += I * b0xcv.x; - } - I = 0.0; // I disappears from metric - } + output.write("Solving high-beta flute reduced equations\n"); + output.write("\tFile : {:s}\n", __FILE__); + output.write("\tCompiled: {:s} at {:s}\n", __DATE__, __TIME__); - ////////////////////////////////////////////////////////////// - // NORMALISE QUANTITIES + ////////////////////////////////////////////////////////////// + // Load data from the grid - if (mesh->get(Bbar, "bmag")) { // Typical magnetic field - Bbar = 1.0; - } - if (mesh->get(Lbar, "rmag")) { // Typical length scale - Lbar = 1.0; - } + // Load 2D profiles + mesh->get(J0, "Jpar0"); // A / m^2 + mesh->get(P0, "pressure"); // Pascals - Va = sqrt(Bbar * Bbar / (MU0 * density * Mi)); + // Load curvature term + b0xcv.covariant = false; // Read contravariant components + mesh->get(b0xcv, "bxcv"); // mixed units x: T y: m^-2 z: m^-2 - Tbar = Lbar / Va; + mesh->get(Psixy, "psixy"); // get Psi + mesh->get(Psiaxis, "psi_axis"); // axis flux + mesh->get(Psibndry, "psi_bndry"); // edge flux + + // Set locations of staggered variables + // Note, use of staggered grids in elm-pb is untested and may not be completely + // implemented. Parallel boundary conditions are especially likely to be wrong. + if (mesh->StaggerGrids) { + loc = CELL_YLOW; + } else { + loc = CELL_CENTRE; + } + Jpar.setLocation(loc); + Vpar.setLocation(loc); + Psi.setLocation(loc); + eta.setLocation(loc); + Psi_loc.setBoundary("Psi_loc"); + + ////////////////////////////////////////////////////////////// + auto &globalOptions = Options::root(); + auto &options = globalOptions["highbeta"]; + + constn0 = options["constn0"].withDefault(true); + // use the hyperbolic profile of n0. If both n0_fake_prof and + // T0_fake_prof are false, use the profiles from grid file + n0_fake_prof = options["n0_fake_prof"].withDefault(false); + // the total height of profile of N0, in percentage of Ni_x + n0_height = options["n0_height"].withDefault(0.4); + // the center or average of N0, in percentage of Ni_x + n0_ave = options["n0_ave"].withDefault(0.01); + // the width of the gradient of N0,in percentage of x + n0_width = options["n0_width"].withDefault(0.1); + // the grid number of the center of N0, in percentage of x + n0_center = options["n0_center"].withDefault(0.633); + // the start of flat region of N0 on SOL side, in percentage of x + n0_bottom_x = options["n0_bottom_x"].withDefault(0.81); + T0_fake_prof = options["T0_fake_prof"].withDefault(false); + // the amplitude of constant temperature, in percentage + Tconst = options["Tconst"].withDefault(-1.0); + + density = options["density"].doc("Number density [m^-3]").withDefault(1.0e19); + + evolve_jpar = options["evolve_jpar"] + .doc("If true, evolve J rather than Psi") + .withDefault(false); + phi_constraint = options["phi_constraint"] + .doc("Use solver constraint for phi?") + .withDefault(false); - dnorm = dia_fact * Mi / (2. * 1.602e-19 * Bbar * Tbar); + // Effects to include/exclude + include_curvature = options["include_curvature"].withDefault(true); + include_jpar0 = options["include_jpar0"].withDefault(true); + evolve_pressure = options["evolve_pressure"].withDefault(true); + nogradparj = options["nogradparj"].withDefault(false); - delta_i = AA * 60.67 * 5.31e5 / sqrt(density / 1e6) / (Lbar * 100.0); + compress = options["compress"] + .doc("Include compressibility effects (evolve Vpar)?") + .withDefault(false); + gyroviscous = options["gyroviscous"].withDefault(false); + nonlinear = options["nonlinear"].doc("Include nonlinear terms?").withDefault(false); + + // option for ExB Poisson Bracket + bm_exb_flag = options["bm_exb_flag"] + .doc("ExB Poisson bracket method. 0=standard;1=simple;2=arakawa") + .withDefault(0); + switch (bm_exb_flag) { + case 0: { + bm_exb = BRACKET_STD; + output << "\tBrackets for ExB: default differencing\n"; + break; + } + case 1: { + bm_exb = BRACKET_SIMPLE; + output << "\tBrackets for ExB: simplified operator\n"; + break; + } + case 2: { + bm_exb = BRACKET_ARAKAWA; + output << "\tBrackets for ExB: Arakawa scheme\n"; + break; + } + case 3: { + bm_exb = BRACKET_CTU; + output << "\tBrackets for ExB: Corner Transport Upwind method\n"; + break; + } + default: + throw BoutException("Invalid choice of bracket method. Must be 0 - 3\n"); + } - output.write("Normalisations: Bbar = {:e} T Lbar = {:e} m\n", Bbar, Lbar); - output.write(" Va = {:e} m/s Tbar = {:e} s\n", Va, Tbar); - output.write(" dnorm = {:e}\n", dnorm); - output.write(" Resistivity\n"); + bm_mag_flag = + options["bm_mag_flag"].doc("magnetic flutter Poisson Bracket").withDefault(0); + switch (bm_mag_flag) { + case 0: { + bm_mag = BRACKET_STD; + output << "\tBrackets: default differencing\n"; + break; + } + case 1: { + bm_mag = BRACKET_SIMPLE; + output << "\tBrackets: simplified operator\n"; + break; + } + case 2: { + bm_mag = BRACKET_ARAKAWA; + output << "\tBrackets: Arakawa scheme\n"; + break; + } + case 3: { + bm_mag = BRACKET_CTU; + output << "\tBrackets: Corner Transport Upwind method\n"; + break; + } + default: + throw BoutException("Invalid choice of bracket method. Must be 0 - 3\n"); + } - if (gyroviscous) { - omega_i = 9.58e7 * Zeff * Bbar; - Upara2 = 0.5 / (Tbar * omega_i); - output.write("Upara2 = {:e} Omega_i = {:e}\n", Upara2, omega_i); - } + eHall = options["eHall"] + .doc("electron Hall or electron parallel pressue gradient effects?") + .withDefault(false); + AA = options["AA"].doc("ion mass in units of proton mass").withDefault(1.0); + + diamag = options["diamag"].doc("Diamagnetic effects?").withDefault(false); + diamag_grad_t = options["diamag_grad_t"] + .doc("Grad_par(Te) term in Psi equation") + .withDefault(diamag); + diamag_phi0 = + options["diamag_phi0"].doc("Include equilibrium phi0").withDefault(diamag); + dia_fact = options["dia_fact"] + .doc("Scale diamagnetic effects by this factor") + .withDefault(1.0); + + // withflow or not + withflow = options["withflow"].withDefault(false); + // keep K-H term + K_H_term = options["K_H_term"].withDefault(true); + // velocity magnitude + D_0 = options["D_0"].withDefault(0.0); + // flowshear + D_s = options["D_s"].withDefault(0.0); + // flow location + x0 = options["x0"].withDefault(0.0); + // flow direction, -1 means negative electric field + sign = options["sign"].withDefault(1.0); + // a constant + D_min = options["D_min"].withDefault(3000.0); + + experiment_Er = options["experiment_Er"].withDefault(false); + + noshear = options["noshear"].withDefault(false); + + relax_j_vac = options["relax_j_vac"] + .doc("Relax vacuum current to zero") + .withDefault(false); + relax_j_tconst = options["relax_j_tconst"] + .doc("Time constant for relaxation of vacuum current. Alfven " + "(normalised) units") + .withDefault(0.1); + + // Toroidal filtering + filter_z = options["filter_z"] + .doc("Filter a single toroidal mode number? The mode to keep is " + "filter_z_mode.") + .withDefault(false); + filter_z_mode = options["filter_z_mode"] + .doc("Single toroidal mode number to keep") + .withDefault(1); + low_pass_z = options["low_pass_z"].doc("Low-pass filter").withDefault(-1); + zonal_flow = options["zonal_flow"] + .doc("Keep zonal (n=0) component of potential?") + .withDefault(false); + zonal_field = options["zonal_field"] + .doc("Keep zonal (n=0) component of magnetic potential?") + .withDefault(false); + zonal_bkgd = options["zonal_bkgd"] + .doc("Evolve zonal (n=0) pressure profile?") + .withDefault(false); - if (eHall) { - output.write(" delta_i = {:e} AA = {:e} \n", delta_i, AA); - } + // n = 0 electrostatic potential solve + split_n0 = options["split_n0"] + .doc("Solve zonal (n=0) component of potential using LaplaceXY?") + .withDefault(false); + if (split_n0) { + // Create an XY solver for n=0 component +#if BOUT_HAS_HYPRE + laplacexy = bout::utils::make_unique(mesh); +#else + laplacexy = bout::utils::make_unique(mesh); +#endif + // Set coefficients for Boussinesq solve + laplacexy->setCoefs(1.0, 0.0); + phi2D = 0.0; // Starting guess + phi2D.setBoundary("phi"); + } - if (vac_lund > 0.0) { - output.write(" Vacuum Tau_R = {:e} s eta = {:e} Ohm m\n", vac_lund * Tbar, - MU0 * Lbar * Lbar / (vac_lund * Tbar)); - vac_resist = 1. / vac_lund; - } else { - output.write(" Vacuum - Zero resistivity -\n"); - vac_resist = 0.0; - } - if (core_lund > 0.0) { - output.write(" Core Tau_R = {:e} s eta = {:e} Ohm m\n", - core_lund * Tbar, MU0 * Lbar * Lbar / (core_lund * Tbar)); - core_resist = 1. / core_lund; - } else { - output.write(" Core - Zero resistivity -\n"); - core_resist = 0.0; - } + // Radial smoothing + smooth_j_x = options["smooth_j_x"].doc("Smooth Jpar in x").withDefault(false); - if (hyperresist > 0.0) { - output.write(" Hyper-resistivity coefficient: {:e}\n", hyperresist); - } + // Jpar boundary region + jpar_bndry_width = options["jpar_bndry_width"] + .doc("Number of cells near the boundary where jpar = 0") + .withDefault(-1); - if (ehyperviscos > 0.0) { - output.write(" electron Hyper-viscosity coefficient: {:e}\n", ehyperviscos); - } + sheath_boundaries = options["sheath_boundaries"] + .doc("Apply sheath boundaries in Y?") + .withDefault(false); - if (hyperviscos > 0.0) { - output.write(" Hyper-viscosity coefficient: {:e}\n", hyperviscos); - SAVE_ONCE(hyper_mu_x); - } + // Parallel differencing + parallel_lr_diff = + options["parallel_lr_diff"] + .doc("Use left and right shifted stencils for parallel differences?") + .withDefault(false); + + // RMP-related options + include_rmp = options["include_rmp"] + .doc("Read RMP field rmp_A from grid?") + .withDefault(false); + + simple_rmp = + options["simple_rmp"].doc("Include a simple RMP model?").withDefault(false); + rmp_factor = options["rmp_factor"].withDefault(1.0); + rmp_ramp = options["rmp_ramp"].withDefault(-1.0); + rmp_freq = options["rmp_freq"].withDefault(-1.0); + rmp_rotate = options["rmp_rotate"].withDefault(0.0); + + // Vacuum region control + vacuum_pressure = + options["vacuum_pressure"] + .doc("Fraction of peak pressure, below which is considered vacuum.") + .withDefault(0.02); + vacuum_trans = + options["vacuum_trans"] + .doc("Vacuum boundary transition width, as fraction of peak pressure.") + .withDefault(0.005); + + // Resistivity and hyper-resistivity options + vac_lund = + options["vac_lund"].doc("Lundquist number in vacuum region").withDefault(0.0); + core_lund = + options["core_lund"].doc("Lundquist number in core region").withDefault(0.0); + hyperresist = options["hyperresist"].withDefault(-1.0); + ehyperviscos = options["ehyperviscos"].withDefault(-1.0); + spitzer_resist = options["spitzer_resist"] + .doc("Use Spitzer resistivity?") + .withDefault(false); + Zeff = options["Zeff"].withDefault(2.0); // Z effective + + // Inner boundary damping + damp_width = options["damp_width"] + .doc("Width of the radial damping regions, in grid cells") + .withDefault(0); + damp_t_const = + options["damp_t_const"] + .doc("Time constant for damping in radial regions. Normalised time units.") + .withDefault(0.1); + + // Viscosity and hyper-viscosity + viscos_par = options["viscos_par"].doc("Parallel viscosity").withDefault(-1.0); + viscos_perp = options["viscos_perp"].doc("Perpendicular viscosity").withDefault(-1.0); + hyperviscos = options["hyperviscos"].doc("Radial hyperviscosity").withDefault(-1.0); + + diffusion_par = + options["diffusion_par"].doc("Parallel pressure diffusion").withDefault(-1.0); + diffusion_p4 = options["diffusion_p4"] + .doc("parallel hyper-viscous diffusion for pressure") + .withDefault(-1.0); + diffusion_u4 = options["diffusion_u4"] + .doc("parallel hyper-viscous diffusion for vorticity") + .withDefault(-1.0); + diffusion_a4 = options["diffusion_a4"] + .doc("parallel hyper-viscous diffusion for vector potential") + .withDefault(-1.0); + + // heating factor in pressure + // heating power in pressure + heating_P = options["heating_P"].withDefault(-1.0); + // the percentage of radial grid points for heating profile radial + // width in pressure + hp_width = options["hp_width"].withDefault(0.1); + // the percentage of radial grid points for heating profile radial + // domain in pressure + hp_length = options["hp_length"].withDefault(0.04); + + // sink factor in pressure + // sink in pressure + sink_P = options["sink_P"].withDefault(-1.0); + // the percentage of radial grid points for sink profile radial + // width in pressure + sp_width = options["sp_width"].withDefault(0.05); + // the percentage of radial grid points for sink profile radial + // domain in pressure + sp_length = options["sp_length"].withDefault(0.04); + + // left edge sink factor in vorticity + // left edge sink in vorticity + sink_Ul = options["sink_Ul"].withDefault(-1.0); + // the percentage of left edge radial grid points for sink profile + // radial width in vorticity + su_widthl = options["su_widthl"].withDefault(0.06); + // the percentage of left edge radial grid points for sink profile + // radial domain in vorticity + su_lengthl = options["su_lengthl"].withDefault(0.15); + + // right edge sink factor in vorticity + // right edge sink in vorticity + sink_Ur = options["sink_Ur"].withDefault(-1.0); + // the percentage of right edge radial grid points for sink profile + // radial width in vorticity + su_widthr = options["su_widthr"].withDefault(0.06); + // the percentage of right edge radial grid points for sink profile + // radial domain in vorticity + su_lengthr = options["su_lengthr"].withDefault(0.15); + + // Compressional terms + phi_curv = + options["phi_curv"].doc("ExB compression in P equation?").withDefault(true); + g = options["gamma"].doc("Ratio of specific heats").withDefault(5.0 / 3.0); + + x = (Psixy - Psiaxis) / (Psibndry - Psiaxis); + + if (experiment_Er) { // get er from experiment + mesh->get(Dphi0, "Epsi"); + diamag_phi0 = false; + K_H_term = false; + } else { + Dphi0 = -D_min - 0.5 * D_0 * (1.0 - tanh(D_s * (x - x0))); + } - if (diffusion_par > 0.0) { - output.write(" diffusion_par: {:e}\n", diffusion_par); - SAVE_ONCE(diffusion_par); - } + if (sign < 0) { // change flow direction + Dphi0 *= -1; + } - // xqx: parallel hyper-viscous diffusion for pressure - if (diffusion_p4 > 0.0) { - output.write(" diffusion_p4: {:e}\n", diffusion_p4); - SAVE_ONCE(diffusion_p4); - } + auto Bpxy = tokamak_options.Bpxy; + auto hthe = tokamak_options.hthe; + auto Rxy = tokamak_options.Rxy; + auto Btxy = tokamak_options.Btxy; + auto B0 = tokamak_options.Bxy; - // xqx: parallel hyper-viscous diffusion for vorticity - if (diffusion_u4 > 0.0) { - output.write(" diffusion_u4: {:e}\n", diffusion_u4); - SAVE_ONCE(diffusion_u4) - } + V0 = -Rxy * Bpxy * Dphi0 / B0; - // xqx: parallel hyper-viscous diffusion for vector potential - if (diffusion_a4 > 0.0) { - output.write(" diffusion_a4: {:e}\n", diffusion_a4); - SAVE_ONCE(diffusion_a4); - } + if (simple_rmp) { + include_rmp = true; + } - if (heating_P > 0.0) { - output.write(" heating_P(watts): {:e}\n", heating_P); + // toroidal and poloidal mode numbers + const int rmp_n = + options["rmp_n"].doc("Simple RMP toroidal mode number").withDefault(3); + const int rmp_m = + options["rmp_m"].doc("Simple RMP poloidal mode number").withDefault(9); + const int rmp_polwid = options["rmp_polwid"] + .doc("Poloidal width (-ve -> full, fraction of 2pi)") + .withDefault(-1.0); + const int rmp_polpeak = options["rmp_polpeak"] + .doc("Peak poloidal location (fraction of 2pi)") + .withDefault(0.5); + rmp_vac_mask = + options["rmp_vac_mask"].doc("Should a vacuum mask be applied?").withDefault(true); + // Divide n by the size of the domain + const int zperiod = globalOptions["zperiod"].withDefault(1); + + if (include_rmp) { + // Including external field coils. + if (simple_rmp) { + // Use a fairly simple form for the perturbation + Field2D pol_angle; + if (mesh->get(pol_angle, "pol_angle")) { + output_warn.write(" ***WARNING: need poloidal angle for simple RMP\n"); + include_rmp = false; + } else { + if ((rmp_n % zperiod) != 0) { + output_warn.write( + " ***WARNING: rmp_n ({:d}) not a multiple of zperiod ({:d})\n", rmp_n, + zperiod); + } + + output.write("\tMagnetic perturbation: n = {:d}, m = {:d}, magnitude {:e} Tm\n", + rmp_n, rmp_m, rmp_factor); + + rmp_Psi0 = 0.0; + if (mesh->lastX()) { + // Set the outer boundary + for (int jx = mesh->LocalNx - 4; jx < mesh->LocalNx; jx++) { + for (int jy = 0; jy < mesh->LocalNy; jy++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + + BoutReal angle = + rmp_m * pol_angle(jx, jy) + + rmp_n * ((BoutReal) jz) * mesh->getCoordinates()->dz(jx, jy, jz); + rmp_Psi0(jx, jy, jz) = + (((BoutReal) (jx - 4)) / ((BoutReal) (mesh->LocalNx - 5))) + * rmp_factor * cos(angle); + if (rmp_polwid > 0.0) { + // Multiply by a Gaussian in poloidal angle + BoutReal gx = + ((pol_angle(jx, jy) / (2. * PI)) - rmp_polpeak) / rmp_polwid; + rmp_Psi0(jx, jy, jz) *= exp(-gx * gx); + } + } + } + } + } + + // Now have a simple model for Psi due to coils at the outer boundary + // Need to calculate Psi inside the domain, enforcing j = 0 + + Jpar = 0.0; + auto psiLap = std::unique_ptr{Laplacian::create(nullptr, loc)}; + psiLap->setInnerBoundaryFlags(INVERT_AC_GRAD); // Zero gradient inner BC + psiLap->setOuterBoundaryFlags(INVERT_SET); // Set to rmp_Psi0 on outer boundary + rmp_Psi0 = psiLap->solve(Jpar, rmp_Psi0); + mesh->communicate(rmp_Psi0); + } + } else { + // Load perturbation from grid file. + include_rmp = !mesh->get(rmp_Psi0, "rmp_A"); // Only include if found + if (!include_rmp) { + output_warn.write("WARNING: Couldn't read 'rmp_A' from grid file\n"); + } + // Multiply by factor + rmp_Psi0 *= rmp_factor; + } + } - output.write(" hp_width(%%): {:e}\n", hp_width); + if (!include_curvature) { + b0xcv = 0.0; + } - output.write(" hp_length(%%): {:e}\n", hp_length); + if (!include_jpar0) { + J0 = 0.0; + } - SAVE_ONCE(heating_P, hp_width, hp_length); - } + ////////////////////////////////////////////////////////////// + // SHIFTED RADIAL COORDINATES - if (sink_P > 0.0) { - output.write(" sink_P(rate): {:e}\n", sink_P); - output.write(" sp_width(%%): {:e}\n", sp_width); - output.write(" sp_length(%%): {:e}\n", sp_length); + BoutReal shearFactor = 1.0; + if (!noshear && mesh->IncIntShear) { + // BOUT-06 style, using d/dx = d/dpsi + I * d/dz + metric->setIntShiftTorsion(tokamak_options.I); - SAVE_ONCE(sink_P, sp_width, sp_length); - } + } else { + // Dimits style, using local coordinate system + if (include_curvature) { + b0xcv.z += tokamak_options.I * b0xcv.x; + } + shearFactor = 0.0; // I disappears from metric + } - if (K_H_term) { - output.write(" keep K-H term\n"); - } else { - output.write(" drop K-H term\n"); - } + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lbar, Bbar, shearFactor); - Field2D Te; - Te = P0 / (2.0 * density * 1.602e-19); // Temperature in eV - - J0 = -MU0 * Lbar * J0 / B0; - P0 = 2.0 * MU0 * P0 / (Bbar * Bbar); - V0 = V0 / Va; - Dphi0 *= Tbar; - - b0xcv.x /= Bbar; - b0xcv.y *= Lbar * Lbar; - b0xcv.z *= Lbar * Lbar; - - Rxy /= Lbar; - Bpxy /= Bbar; - Btxy /= Bbar; - B0 /= Bbar; - hthe /= Lbar; - metric->setDx(metric->dx() / (Lbar * Lbar * Bbar)); - I *= Lbar * Lbar * Bbar; - - if (constn0) { - T0_fake_prof = false; - n0_fake_prof = false; - } else { - Nbar = 1.0; - Tibar = 1000.0; - Tebar = 1000.0; - - if ((!T0_fake_prof) && n0_fake_prof) { - N0 = N0tanh(n0_height * Nbar, n0_ave * Nbar, n0_width, n0_center, n0_bottom_x); - - Ti0 = P0 / N0 / 2.0; - Te0 = Ti0; - } else if (T0_fake_prof) { - Ti0 = Tconst; - Te0 = Ti0; - N0 = P0 / (Ti0 + Te0); - } else { - if (mesh->get(N0, "Niexp")) { // N_i0 - throw BoutException("Error: Cannot read Ni0 from grid\n"); - } - - if (mesh->get(Ti0, "Tiexp")) { // T_i0 - throw BoutException("Error: Cannot read Ti0 from grid\n"); - } - - if (mesh->get(Te0, "Teexp")) { // T_e0 - throw BoutException("Error: Cannot read Te0 from grid\n"); - } - N0 /= Nbar; - Ti0 /= Tibar; - Te0 /= Tebar; - } - } + ////////////////////////////////////////////////////////////// + // NORMALISE QUANTITIES - if (gyroviscous) { - Dperp2Phi0.setLocation(CELL_CENTRE); - Dperp2Phi0.setBoundary("phi"); - Dperp2Phi.setLocation(CELL_CENTRE); - Dperp2Phi.setBoundary("phi"); - GradPhi02.setLocation(CELL_CENTRE); - GradPhi02.setBoundary("phi"); - GradcPhi.setLocation(CELL_CENTRE); - GradcPhi.setBoundary("phi"); - Dperp2Pi0.setLocation(CELL_CENTRE); - Dperp2Pi0.setBoundary("P"); - Dperp2Pi.setLocation(CELL_CENTRE); - Dperp2Pi.setBoundary("P"); - bracketPhi0P.setLocation(CELL_CENTRE); - bracketPhi0P.setBoundary("P"); - bracketPhiP0.setLocation(CELL_CENTRE); - bracketPhiP0.setBoundary("P"); - if (nonlinear) { - GradPhi2.setLocation(CELL_CENTRE); - GradPhi2.setBoundary("phi"); - bracketPhiP.setLocation(CELL_CENTRE); - bracketPhiP.setBoundary("P"); - } - } + if (mesh->get(Bbar, "bmag")) { // Typical magnetic field + Bbar = 1.0; + } + if (mesh->get(Lbar, "rmag")) { // Typical length scale + Lbar = 1.0; + } - BoutReal pnorm = max(P0, true); // Maximum over all processors - - vacuum_pressure *= pnorm; // Get pressure from fraction - vacuum_trans *= pnorm; - - // Transitions from 0 in core to 1 in vacuum - vac_mask = (1.0 - tanh((P0 - vacuum_pressure) / vacuum_trans)) / 2.0; - - if (spitzer_resist) { - // Use Spitzer resistivity - output.write("\tTemperature: {:e} -> {:e} [eV]\n", min(Te), max(Te)); - eta = 0.51 * 1.03e-4 * Zeff * 20. - * pow(Te, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 - output.write("\tSpitzer resistivity: {:e} -> {:e} [Ohm m]\n", min(eta), max(eta)); - eta /= MU0 * Va * Lbar; - output.write("\t -> Lundquist {:e} -> {:e}\n", 1.0 / max(eta), 1.0 / min(eta)); - } else { - // transition from 0 for large P0 to resistivity for small P0 - eta = core_resist + (vac_resist - core_resist) * vac_mask; - } + Va = sqrt(Bbar * Bbar / (MU0 * density * Mi)); - eta = interp_to(eta, loc); - - SAVE_ONCE(eta); - - if (include_rmp) { - // Normalise RMP quantities - - rmp_Psi0 /= Bbar * Lbar; - - rmp_ramp /= Tbar; - rmp_freq *= Tbar; - rmp_rotate *= Tbar; - - rmp_Psi = rmp_Psi0; - rmp_dApdt = 0.0; - - bool apar_changing = false; - - output.write("Including magnetic perturbation\n"); - if (rmp_ramp > 0.0) { - output.write("\tRamping up over period t = {:e} ({:e} ms)\n", rmp_ramp, - rmp_ramp * Tbar * 1000.); - apar_changing = true; - } - if (rmp_freq > 0.0) { - output.write("\tOscillating with frequency f = {:e} ({:e} kHz)\n", rmp_freq, - rmp_freq / Tbar / 1000.); - apar_changing = true; - } - if (rmp_rotate != 0.0) { - output.write("\tRotating with a frequency f = {:e} ({:e} kHz)\n", rmp_rotate, - rmp_rotate / Tbar / 1000.); - apar_changing = true; - } - - if (apar_changing) { - SAVE_REPEAT(rmp_Psi, rmp_dApdt); - } else { - SAVE_ONCE(rmp_Psi); - } - } else { - rmp_Psi = 0.0; - } + Tbar = Lbar / Va; - /**************** CALCULATE METRICS ******************/ + dnorm = dia_fact * Mi / (2. * 1.602e-19 * Bbar * Tbar); - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(I) * g11 + SQ(B0) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); + delta_i = AA * 60.67 * 5.31e5 / sqrt(density / 1e6) / (Lbar * 100.0); - const auto g_11 = 1.0 / g11 + SQ(I * Rxy); - const auto g_22 = SQ(B0 * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; + output.write("Normalisations: Bbar = {:e} T Lbar = {:e} m\n", Bbar, Lbar); + output.write(" Va = {:e} m/s Tbar = {:e} s\n", Va, Tbar); + output.write(" dnorm = {:e}\n", dnorm); + output.write(" Resistivity\n"); - metric->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); + if (gyroviscous) { + omega_i = 9.58e7 * Zeff * Bbar; + Upara2 = 0.5 / (Tbar * omega_i); + output.write("Upara2 = {:e} Omega_i = {:e}\n", Upara2, omega_i); + } - metric->setJ(hthe / Bpxy); - metric->setBxy(B0); + if (eHall) { + output.write(" delta_i = {:e} AA = {:e} \n", delta_i, AA); + } - // Set B field vector + if (vac_lund > 0.0) { + output.write(" Vacuum Tau_R = {:e} s eta = {:e} Ohm m\n", vac_lund * Tbar, + MU0 * Lbar * Lbar / (vac_lund * Tbar)); + vac_resist = 1. / vac_lund; + } else { + output.write(" Vacuum - Zero resistivity -\n"); + vac_resist = 0.0; + } + if (core_lund > 0.0) { + output.write(" Core Tau_R = {:e} s eta = {:e} Ohm m\n", + core_lund * Tbar, MU0 * Lbar * Lbar / (core_lund * Tbar)); + core_resist = 1. / core_lund; + } else { + output.write(" Core - Zero resistivity -\n"); + core_resist = 0.0; + } - B0vec.covariant = false; - B0vec.x = 0.; - B0vec.y = Bpxy / hthe; - B0vec.z = 0.; + if (hyperresist > 0.0) { + output.write(" Hyper-resistivity coefficient: {:e}\n", hyperresist); + } - V0net.covariant = false; // presentation for net flow - V0net.x = 0.; - V0net.y = Rxy * Btxy * Bpxy / (hthe * B0 * B0) * Dphi0; - V0net.z = -Dphi0; + if (ehyperviscos > 0.0) { + output.write(" electron Hyper-viscosity coefficient: {:e}\n", ehyperviscos); + } - U0 = B0vec * Curl(V0net) / B0; // get 0th vorticity for Kelvin-Holmholtz term + if (hyperviscos > 0.0) { + output.write(" Hyper-viscosity coefficient: {:e}\n", hyperviscos); + SAVE_ONCE(hyper_mu_x); + } - /**************** SET EVOLVING VARIABLES *************/ + if (diffusion_par > 0.0) { + output.write(" diffusion_par: {:e}\n", diffusion_par); + SAVE_ONCE(diffusion_par); + } - // Tell BOUT which variables to evolve - SOLVE_FOR(U, P); + // xqx: parallel hyper-viscous diffusion for pressure + if (diffusion_p4 > 0.0) { + output.write(" diffusion_p4: {:e}\n", diffusion_p4); + SAVE_ONCE(diffusion_p4); + } - if (evolve_jpar) { - output.write("Solving for jpar: Inverting to get Psi\n"); - SOLVE_FOR(Jpar); - SAVE_REPEAT(Psi); - } else { - output.write("Solving for Psi, Differentiating to get jpar\n"); - SOLVE_FOR(Psi); - SAVE_REPEAT(Jpar); - } + // xqx: parallel hyper-viscous diffusion for vorticity + if (diffusion_u4 > 0.0) { + output.write(" diffusion_u4: {:e}\n", diffusion_u4); + SAVE_ONCE(diffusion_u4) + } - if (compress) { - output.write("Including compression (Vpar) effects\n"); + // xqx: parallel hyper-viscous diffusion for vector potential + if (diffusion_a4 > 0.0) { + output.write(" diffusion_a4: {:e}\n", diffusion_a4); + SAVE_ONCE(diffusion_a4); + } - SOLVE_FOR(Vpar); - comms.add(Vpar); + if (heating_P > 0.0) { + output.write(" heating_P(watts): {:e}\n", heating_P); - beta = B0 * B0 / (0.5 + (B0 * B0 / (g * P0))); - gradparB = Grad_par(B0) / B0; + output.write(" hp_width(%%): {:e}\n", hp_width); - output.write("Beta in range {:e} -> {:e}\n", min(beta), max(beta)); - } else { - Vpar = 0.0; - } + output.write(" hp_length(%%): {:e}\n", hp_length); - if (phi_constraint) { - // Implicit Phi solve using IDA + SAVE_ONCE(heating_P, hp_width, hp_length); + } - if (!solver->constraints()) { - throw BoutException("Cannot constrain. Run again with phi_constraint=false.\n"); - } + if (sink_P > 0.0) { + output.write(" sink_P(rate): {:e}\n", sink_P); + output.write(" sp_width(%%): {:e}\n", sp_width); + output.write(" sp_length(%%): {:e}\n", sp_length); - solver->constraint(phi, C_phi, "phi"); + SAVE_ONCE(sink_P, sp_width, sp_length); + } - // Set preconditioner - setPrecon(&ELMpb::precon_phi); + if (K_H_term) { + output.write(" keep K-H term\n"); + } else { + output.write(" drop K-H term\n"); + } - } else { - // Phi solved in RHS (explicitly) - SAVE_REPEAT(phi); + Field2D Te; + Te = P0 / (2.0 * density * 1.602e-19); // Temperature in eV - // Set preconditioner - setPrecon(&ELMpb::precon); + J0 = -MU0 * Lbar * J0 / B0; + P0 = 2.0 * MU0 * P0 / (Bbar * Bbar); + V0 = V0 / Va; + Dphi0 *= Tbar; - // Set Jacobian - setJacobian((jacobianfunc)&ELMpb::jacobian); - } + b0xcv.x /= Bbar; + b0xcv.y *= Lbar * Lbar; + b0xcv.z *= Lbar * Lbar; - // Diamagnetic phi0 - if (diamag_phi0) { - if (constn0) { - phi0 = -0.5 * dnorm * P0 / B0; - } else { - // Stationary equilibrium plasma. ExB velocity balances diamagnetic drift - phi0 = -0.5 * dnorm * P0 / B0 / N0; - } - SAVE_ONCE(phi0); - } + if (constn0) { + T0_fake_prof = false; + n0_fake_prof = false; + } else { + Nbar = 1.0; + Tibar = 1000.0; + Tebar = 1000.0; + + if ((!T0_fake_prof) && n0_fake_prof) { + N0 = N0tanh(n0_height * Nbar, n0_ave * Nbar, n0_width, n0_center, n0_bottom_x); + + Ti0 = P0 / N0 / 2.0; + Te0 = Ti0; + } else if (T0_fake_prof) { + Ti0 = Tconst; + Te0 = Ti0; + N0 = P0 / (Ti0 + Te0); + } else { + if (mesh->get(N0, "Niexp")) { // N_i0 + throw BoutException("Error: Cannot read Ni0 from grid\n"); + } - // Add some equilibrium quantities and normalisations - // everything needed to recover physical units - SAVE_ONCE(J0, P0); - SAVE_ONCE(density, Lbar, Bbar, Tbar); - SAVE_ONCE(Va, B0); - SAVE_ONCE(Dphi0, U0); - SAVE_ONCE(V0); - if (!constn0) { - SAVE_ONCE(Ti0, Te0, N0); - } + if (mesh->get(Ti0, "Tiexp")) { // T_i0 + throw BoutException("Error: Cannot read Ti0 from grid\n"); + } - // Create a solver for the Laplacian - phiSolver = Laplacian::create(&globalOptions["phiSolver"]); - // Save performance metrics to output, using the - // given name as the prefix. - phiSolver->savePerformance(*solver, "phiSolver"); + if (mesh->get(Te0, "Teexp")) { // T_e0 + throw BoutException("Error: Cannot read Te0 from grid\n"); + } + N0 /= Nbar; + Ti0 /= Tibar; + Te0 /= Tebar; + } + } - aparSolver = Laplacian::create(&globalOptions["aparSolver"], loc); + if (gyroviscous) { + Dperp2Phi0.setLocation(CELL_CENTRE); + Dperp2Phi0.setBoundary("phi"); + Dperp2Phi.setLocation(CELL_CENTRE); + Dperp2Phi.setBoundary("phi"); + GradPhi02.setLocation(CELL_CENTRE); + GradPhi02.setBoundary("phi"); + GradcPhi.setLocation(CELL_CENTRE); + GradcPhi.setBoundary("phi"); + Dperp2Pi0.setLocation(CELL_CENTRE); + Dperp2Pi0.setBoundary("P"); + Dperp2Pi.setLocation(CELL_CENTRE); + Dperp2Pi.setBoundary("P"); + bracketPhi0P.setLocation(CELL_CENTRE); + bracketPhi0P.setBoundary("P"); + bracketPhiP0.setLocation(CELL_CENTRE); + bracketPhiP0.setBoundary("P"); + if (nonlinear) { + GradPhi2.setLocation(CELL_CENTRE); + GradPhi2.setBoundary("phi"); + bracketPhiP.setLocation(CELL_CENTRE); + bracketPhiP.setBoundary("P"); + } + } - /////////////// CHECK VACUUM /////////////////////// - // In vacuum region, initial vorticity should equal zero + BoutReal pnorm = max(P0, true); // Maximum over all processors - if (!restarting) { - // Only if not restarting: Check initial perturbation + vacuum_pressure *= pnorm; // Get pressure from fraction + vacuum_trans *= pnorm; - // Set U to zero where P0 < vacuum_pressure - U = where(P0 - vacuum_pressure, U, 0.0); + // Transitions from 0 in core to 1 in vacuum + vac_mask = (1.0 - tanh((P0 - vacuum_pressure) / vacuum_trans)) / 2.0; - if (constn0) { - ubyn = U; - // Phi should be consistent with U - phi = phiSolver->solve(ubyn); - } else { - ubyn = U / N0; - phiSolver->setCoefC(N0); - phi = phiSolver->solve(ubyn); - } + if (spitzer_resist) { + // Use Spitzer resistivity + output.write("\tTemperature: {:e} -> {:e} [eV]\n", min(Te), max(Te)); + eta = 0.51 * 1.03e-4 * Zeff * 20. + * pow(Te, -1.5); // eta in Ohm-m. NOTE: ln(Lambda) = 20 + output.write("\tSpitzer resistivity: {:e} -> {:e} [Ohm m]\n", min(eta), max(eta)); + eta /= MU0 * Va * Lbar; + output.write("\t -> Lundquist {:e} -> {:e}\n", 1.0 / max(eta), 1.0 / min(eta)); + } else { + // transition from 0 for large P0 to resistivity for small P0 + eta = core_resist + (vac_resist - core_resist) * vac_mask; + } - // if(diamag) { - // phi -= 0.5*dnorm * P / B0; - //} - } + eta = interp_to(eta, loc); - /************** SETUP COMMUNICATIONS **************/ + SAVE_ONCE(eta); - comms.add(U, P); + if (include_rmp) { + // Normalise RMP quantities - phi.setBoundary("phi"); // Set boundary conditions - tmpU2.setBoundary("U"); - tmpP2.setBoundary("P"); - tmpA2.setBoundary("J"); + rmp_Psi0 /= Bbar * Lbar; - if (evolve_jpar) { - comms.add(Jpar); - } else { - comms.add(Psi); - // otherwise Need to communicate Jpar separately - Jpar.setBoundary("J"); - } - Jpar2.setBoundary("J"); + rmp_ramp /= Tbar; + rmp_freq *= Tbar; + rmp_rotate *= Tbar; - return 0; - } + rmp_Psi = rmp_Psi0; + rmp_dApdt = 0.0; - // Parallel gradient along perturbed field-line - Field3D Grad_parP(const Field3D& f, CELL_LOC loc = CELL_DEFAULT) const { + bool apar_changing = false; - if (loc == CELL_DEFAULT) { - loc = f.getLocation(); - } + output.write("Including magnetic perturbation\n"); + if (rmp_ramp > 0.0) { + output.write("\tRamping up over period t = {:e} ({:e} ms)\n", rmp_ramp, + rmp_ramp * Tbar * 1000.); + apar_changing = true; + } + if (rmp_freq > 0.0) { + output.write("\tOscillating with frequency f = {:e} ({:e} kHz)\n", rmp_freq, + rmp_freq / Tbar / 1000.); + apar_changing = true; + } + if (rmp_rotate != 0.0) { + output.write("\tRotating with a frequency f = {:e} ({:e} kHz)\n", rmp_rotate, + rmp_rotate / Tbar / 1000.); + apar_changing = true; + } - Field3D result = Grad_par(f, loc); + if (apar_changing) { + SAVE_REPEAT(rmp_Psi, rmp_dApdt); + } else { + SAVE_ONCE(rmp_Psi); + } + } else { + rmp_Psi = 0.0; + } - if (nonlinear) { - result -= bracket(interp_to(Psi, loc), f, bm_mag) * B0; + // Set B field vector - if (include_rmp) { - result -= bracket(interp_to(rmp_Psi, loc), f, bm_mag) * B0; - } - } + B0vec.covariant = false; + B0vec.x = 0.; - return result; - } + B0vec.y = Bpxy / hthe; + B0vec.z = 0.; - bool first_run = true; // For printing out some diagnostics first time around + V0net.covariant = false; // presentation for net flow + V0net.x = 0.; + V0net.y = Rxy * Btxy * Bpxy / (hthe * B0 * B0) * Dphi0; + V0net.z = -Dphi0; - int rhs(BoutReal t) override { - // Perform communications - mesh->communicate(comms); + U0 = B0vec * Curl(V0net) / B0; // get 0th vorticity for Kelvin-Holmholtz term - Coordinates* metric = mesh->getCoordinates(); + /**************** SET EVOLVING VARIABLES *************/ - //////////////////////////////////////////// - // Transitions from 0 in core to 1 in vacuum - if (nonlinear) { - vac_mask = (1.0 - tanh(((P0 + P) - vacuum_pressure) / vacuum_trans)) / 2.0; + // Tell BOUT which variables to evolve + SOLVE_FOR(U, P); - // Update resistivity - if (spitzer_resist) { - // Use Spitzer formula - Field3D Te; - Te = (P0 + P) * Bbar * Bbar / (4. * MU0) / (density * 1.602e-19); // eV + if (evolve_jpar) { + output.write("Solving for jpar: Inverting to get Psi\n"); + SOLVE_FOR(Jpar); + SAVE_REPEAT(Psi); + } else { + output.write("Solving for Psi, Differentiating to get jpar\n"); + SOLVE_FOR(Psi); + SAVE_REPEAT(Jpar); + } - // eta in Ohm-m. ln(Lambda) = 20 - eta = interp_to(0.51 * 1.03e-4 * Zeff * 20. * pow(Te, -1.5), loc); + if (compress) { + output.write("Including compression (Vpar) effects\n"); - // Normalised eta - eta /= MU0 * Va * Lbar; - } else { - // Use specified core and vacuum Lundquist numbers - eta = core_resist + (vac_resist - core_resist) * vac_mask; - } - eta = interp_to(eta, loc); - } + SOLVE_FOR(Vpar); + comms.add(Vpar); - //////////////////////////////////////////// - // Resonant Magnetic Perturbation code + beta = B0 * B0 / (0.5 + (B0 * B0 / (g * P0))); + gradparB = Grad_par(B0) / B0; - if (include_rmp) { + output.write("Beta in range {:e} -> {:e}\n", min(beta), max(beta)); + } else { + Vpar = 0.0; + } + + if (phi_constraint) { + // Implicit Phi solve using IDA - if ((rmp_ramp > 0.0) || (rmp_freq > 0.0) || (rmp_rotate != 0.0)) { - // Need to update the RMP terms + if (!solver->constraints()) { + throw BoutException("Cannot constrain. Run again with phi_constraint=false.\n"); + } - if ((rmp_ramp > 0.0) && (t < rmp_ramp)) { - // Still in ramp phase + solver->constraint(phi, C_phi, "phi"); - rmp_Psi = (t / rmp_ramp) * rmp_Psi0; // Linear ramp + // Set preconditioner + setPrecon(&ELMpb::precon_phi); - rmp_dApdt = rmp_Psi0 / rmp_ramp; } else { - rmp_Psi = rmp_Psi0; - rmp_dApdt = 0.0; + // Phi solved in RHS (explicitly) + SAVE_REPEAT(phi); + + // Set preconditioner + setPrecon(&ELMpb::precon); + + // Set Jacobian + setJacobian((jacobianfunc) & ELMpb::jacobian); + } + + // Diamagnetic phi0 + if (diamag_phi0) { + if (constn0) { + phi0 = -0.5 * dnorm * P0 / B0; + } else { + // Stationary equilibrium plasma. ExB velocity balances diamagnetic drift + phi0 = -0.5 * dnorm * P0 / B0 / N0; + } + SAVE_ONCE(phi0); + } + + // Add some equilibrium quantities and normalisations + // everything needed to recover physical units + SAVE_ONCE(J0, P0); + SAVE_ONCE(density, Lbar, Bbar, Tbar); + SAVE_ONCE(Va, B0); + SAVE_ONCE(Dphi0, U0); + SAVE_ONCE(V0); + if (!constn0) { + SAVE_ONCE(Ti0, Te0, N0); } - if (rmp_freq > 0.0) { - // Oscillating the amplitude + // Create a solver for the Laplacian + phiSolver = Laplacian::create(&globalOptions["phiSolver"]); - rmp_dApdt = rmp_dApdt * sin(2. * PI * rmp_freq * t) - + rmp_Psi * (2. * PI * rmp_freq) * cos(2. * PI * rmp_freq * t); + aparSolver = Laplacian::create(&globalOptions["aparSolver"], loc); - rmp_Psi *= sin(2. * PI * rmp_freq * t); + /////////////// CHECK VACUUM /////////////////////// + // In vacuum region, initial vorticity should equal zero + + if (!restarting) { + // Only if not restarting: Check initial perturbation + + // Set U to zero where P0 < vacuum_pressure + U = where(P0 - vacuum_pressure, U, 0.0); + + if (constn0) { + ubyn = U; + // Phi should be consistent with U + phi = phiSolver->solve(ubyn); + } else { + ubyn = U / N0; + phiSolver->setCoefC(N0); + phi = phiSolver->solve(ubyn); + } + + // if(diamag) { + // phi -= 0.5*dnorm * P / B0; + //} } - if (rmp_rotate != 0.0) { - // Rotate toroidally at given frequency + /************** SETUP COMMUNICATIONS **************/ - shiftZ(rmp_Psi, 2 * PI * rmp_rotate * t); - shiftZ(rmp_dApdt, 2 * PI * rmp_rotate * t); + comms.add(U, P); - // Add toroidal rotation term. CHECK SIGN + phi.setBoundary("phi"); // Set boundary conditions + tmpU2.setBoundary("U"); + tmpP2.setBoundary("P"); + tmpA2.setBoundary("J"); - rmp_dApdt += DDZ(rmp_Psi) * 2 * PI * rmp_rotate; + if (evolve_jpar) { + comms.add(Jpar); + } else { + comms.add(Psi); + // otherwise Need to communicate Jpar separately + Jpar.setBoundary("J"); } + Jpar2.setBoundary("J"); + + return 0; + } - // Set to zero in the core - if (rmp_vac_mask) { - rmp_Psi *= vac_mask; + // Parallel gradient along perturbed field-line + Field3D Grad_parP(const Field3D &f, CELL_LOC loc = CELL_DEFAULT) const { + + if (loc == CELL_DEFAULT) { + loc = f.getLocation(); } - } else { - // Set to zero in the core region - if (rmp_vac_mask) { - // Only in vacuum -> skin current -> diffuses inwards - rmp_Psi = rmp_Psi0 * vac_mask; + + Field3D result = Grad_par(f, loc); + + auto B0 = tokamak_options.Bxy; + + if (nonlinear) { + result -= bracket(interp_to(Psi, loc), f, bm_mag) * B0; + + if (include_rmp) { + result -= bracket(interp_to(rmp_Psi, loc), f, bm_mag) * B0; + } } - } - mesh->communicate(rmp_Psi); + return result; } - //////////////////////////////////////////// - // Inversion + bool first_run = true; // For printing out some diagnostics first time around - if (evolve_jpar) { - // Invert laplacian for Psi - Psi = aparSolver->solve(Jpar); - mesh->communicate(Psi); - } + int rhs(BoutReal t) override { + // Perform communications + mesh->communicate(comms); - if (phi_constraint) { - // Phi being solved as a constraint + Coordinates *metric = mesh->getCoordinates(); - Field3D Ctmp = phi; - Ctmp.setBoundary("phi"); // Look up boundary conditions for phi - Ctmp.applyBoundary(); - Ctmp -= phi; // Now contains error in the boundary + auto B0 = tokamak_options.Bxy; - C_phi = Delp2(phi) - U; // Error in the bulk - C_phi.setBoundaryTo(Ctmp); + //////////////////////////////////////////// + // Transitions from 0 in core to 1 in vacuum + if (nonlinear) { + vac_mask = (1.0 - tanh(((P0 + P) - vacuum_pressure) / vacuum_trans)) / 2.0; - } else { + // Update resistivity + if (spitzer_resist) { + // Use Spitzer formula + Field3D Te; + Te = (P0 + P) * Bbar * Bbar / (4. * MU0) / (density * 1.602e-19); // eV - if (constn0) { - if (split_n0) { - //////////////////////////////////////////// - // Boussinesq, split - // Split into axisymmetric and non-axisymmetric components - Field2D Vort2D = DC(U); // n=0 component + // eta in Ohm-m. ln(Lambda) = 20 + eta = interp_to(0.51 * 1.03e-4 * Zeff * 20. * pow(Te, -1.5), loc); - // Applies boundary condition for "phi". - phi2D.applyBoundary(t); + // Normalised eta + eta /= MU0 * Va * Lbar; + } else { + // Use specified core and vacuum Lundquist numbers + eta = core_resist + (vac_resist - core_resist) * vac_mask; + } + eta = interp_to(eta, loc); + } - // Solve axisymmetric (n=0) part - phi2D = laplacexy->solve(Vort2D, phi2D); + //////////////////////////////////////////// + // Resonant Magnetic Perturbation code - // Solve non-axisymmetric part - phi = phiSolver->solve(U - Vort2D); + if (include_rmp) { - phi += phi2D; // Add axisymmetric part - } else { - phi = phiSolver->solve(U); + if ((rmp_ramp > 0.0) || (rmp_freq > 0.0) || (rmp_rotate != 0.0)) { + // Need to update the RMP terms + + if ((rmp_ramp > 0.0) && (t < rmp_ramp)) { + // Still in ramp phase + + rmp_Psi = (t / rmp_ramp) * rmp_Psi0; // Linear ramp + + rmp_dApdt = rmp_Psi0 / rmp_ramp; + } else { + rmp_Psi = rmp_Psi0; + rmp_dApdt = 0.0; + } + + if (rmp_freq > 0.0) { + // Oscillating the amplitude + + rmp_dApdt = rmp_dApdt * sin(2. * PI * rmp_freq * t) + + rmp_Psi * (2. * PI * rmp_freq) * cos(2. * PI * rmp_freq * t); + + rmp_Psi *= sin(2. * PI * rmp_freq * t); + } + + if (rmp_rotate != 0.0) { + // Rotate toroidally at given frequency + + shiftZ(rmp_Psi, 2 * PI * rmp_rotate * t); + shiftZ(rmp_dApdt, 2 * PI * rmp_rotate * t); + + // Add toroidal rotation term. CHECK SIGN + + rmp_dApdt += DDZ(rmp_Psi) * 2 * PI * rmp_rotate; + } + + // Set to zero in the core + if (rmp_vac_mask) { + rmp_Psi *= vac_mask; + } + } else { + // Set to zero in the core region + if (rmp_vac_mask) { + // Only in vacuum -> skin current -> diffuses inwards + rmp_Psi = rmp_Psi0 * vac_mask; + } + } + + mesh->communicate(rmp_Psi); } - if (diamag) { - phi -= 0.5 * dnorm * P / B0; + //////////////////////////////////////////// + // Inversion + + if (evolve_jpar) { + // Invert laplacian for Psi + Psi = aparSolver->solve(Jpar); + mesh->communicate(Psi); } - } else { - ubyn = U / N0; - if (diamag) { - ubyn -= 0.5 * dnorm / (N0 * B0) * Delp2(P); - mesh->communicate(ubyn); + + if (phi_constraint) { + // Phi being solved as a constraint + + Field3D Ctmp = phi; + Ctmp.setBoundary("phi"); // Look up boundary conditions for phi + Ctmp.applyBoundary(); + Ctmp -= phi; // Now contains error in the boundary + + C_phi = Delp2(phi) - U; // Error in the bulk + C_phi.setBoundaryTo(Ctmp); + + } else { + + if (constn0) { + if (split_n0) { + //////////////////////////////////////////// + // Boussinesq, split + // Split into axisymmetric and non-axisymmetric components + Field2D Vort2D = DC(U); // n=0 component + + // Applies boundary condition for "phi". + phi2D.applyBoundary(t); + + // Solve axisymmetric (n=0) part + phi2D = laplacexy->solve(Vort2D, phi2D); + + // Solve non-axisymmetric part + phi = phiSolver->solve(U - Vort2D); + + phi += phi2D; // Add axisymmetric part + } else { + phi = phiSolver->solve(U); + } + + if (diamag) { + phi -= 0.5 * dnorm * P / B0; + } + } else { + ubyn = U / N0; + if (diamag) { + ubyn -= 0.5 * dnorm / (N0 * B0) * Delp2(P); + mesh->communicate(ubyn); + } + // Invert laplacian for phi + phiSolver->setCoefC(N0); + phi = phiSolver->solve(ubyn); + } + // Apply a boundary condition on phi for target plates + phi.applyBoundary(); + mesh->communicate(phi); } - // Invert laplacian for phi - phiSolver->setCoefC(N0); - phi = phiSolver->solve(ubyn); - } - // Apply a boundary condition on phi for target plates - phi.applyBoundary(); - mesh->communicate(phi); - } - if (!evolve_jpar) { - // Get J from Psi - Jpar = Delp2(Psi); - if (include_rmp) { - Jpar += Delp2(rmp_Psi); - } - - Jpar.applyBoundary(); - mesh->communicate(Jpar); - - if (jpar_bndry_width > 0) { - // Zero j in boundary regions. Prevents vorticity drive - // at the boundary - - for (int i = 0; i < jpar_bndry_width; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - if (mesh->firstX()) { - Jpar(i, j, k) = 0.0; - } - if (mesh->lastX()) { - Jpar(mesh->LocalNx - 1 - i, j, k) = 0.0; - } + if (!evolve_jpar) { + // Get J from Psi + Jpar = Delp2(Psi); + if (include_rmp) { + Jpar += Delp2(rmp_Psi); } - } - } - } - - // Smooth j in x - if (smooth_j_x) { - Jpar = smooth_x(Jpar); - Jpar.applyBoundary(); - - // Recommunicate now smoothed - mesh->communicate(Jpar); - } - - // Get Delp2(J) from J - Jpar2 = Delp2(Jpar); - - Jpar2.applyBoundary(); - mesh->communicate(Jpar2); - - if (jpar_bndry_width > 0) { - // Zero jpar2 in boundary regions. Prevents vorticity drive - // at the boundary - - for (int i = 0; i < jpar_bndry_width; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - if (mesh->firstX()) { - Jpar2(i, j, k) = 0.0; - } - if (mesh->lastX()) { - Jpar2(mesh->LocalNx - 1 - i, j, k) = 0.0; - } + + Jpar.applyBoundary(); + mesh->communicate(Jpar); + + if (jpar_bndry_width > 0) { + // Zero j in boundary regions. Prevents vorticity drive + // at the boundary + + for (int i = 0; i < jpar_bndry_width; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + if (mesh->firstX()) { + Jpar(i, j, k) = 0.0; + } + if (mesh->lastX()) { + Jpar(mesh->LocalNx - 1 - i, j, k) = 0.0; + } + } + } + } + } + + // Smooth j in x + if (smooth_j_x) { + Jpar = smooth_x(Jpar); + Jpar.applyBoundary(); + + // Recommunicate now smoothed + mesh->communicate(Jpar); + } + + // Get Delp2(J) from J + Jpar2 = Delp2(Jpar); + + Jpar2.applyBoundary(); + mesh->communicate(Jpar2); + + if (jpar_bndry_width > 0) { + // Zero jpar2 in boundary regions. Prevents vorticity drive + // at the boundary + + for (int i = 0; i < jpar_bndry_width; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + if (mesh->firstX()) { + Jpar2(i, j, k) = 0.0; + } + if (mesh->lastX()) { + Jpar2(mesh->LocalNx - 1 - i, j, k) = 0.0; + } + } + } + } } - } } - } - } - //////////////////////////////////////////////////// - // Sheath boundary conditions - // Normalised and linearised, since here we have only pressure - // rather than density and temperature. Applying a boundary - // to Jpar so that Jpar = sqrt(mi/me)/(2*pi) * phi - // + //////////////////////////////////////////////////// + // Sheath boundary conditions + // Normalised and linearised, since here we have only pressure + // rather than density and temperature. Applying a boundary + // to Jpar so that Jpar = sqrt(mi/me)/(2*pi) * phi + // - if (sheath_boundaries) { + if (sheath_boundaries) { - // Need to shift into field-aligned coordinates before applying - // parallel boundary conditions + // Need to shift into field-aligned coordinates before applying + // parallel boundary conditions - auto phi_fa = toFieldAligned(phi); - auto P_fa = toFieldAligned(P); - auto Jpar_fa = toFieldAligned(Jpar); + auto phi_fa = toFieldAligned(phi); + auto P_fa = toFieldAligned(P); + auto Jpar_fa = toFieldAligned(Jpar); - // At y = ystart (lower boundary) + // At y = ystart (lower boundary) - for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { + for (RangeIterator r = mesh->iterateBndryLowerY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { + + // Zero-gradient potential + BoutReal const phisheath = phi_fa(r.ind, mesh->ystart, jz); + + BoutReal jsheath = -(sqrt(mi_me) / (2. * sqrt(PI))) * phisheath; + + // Apply boundary condition half-way between cells + for (int jy = mesh->ystart - 1; jy >= 0; jy--) { + // Neumann conditions + P_fa(r.ind, jy, jz) = P_fa(r.ind, mesh->ystart, jz); + phi_fa(r.ind, jy, jz) = phisheath; + // Dirichlet condition on Jpar + Jpar_fa(r.ind, jy, jz) = 2. * jsheath - Jpar_fa(r.ind, mesh->ystart, jz); + } + } + } + + // At y = yend (upper boundary) - // Zero-gradient potential - BoutReal const phisheath = phi_fa(r.ind, mesh->ystart, jz); + for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { + for (int jz = 0; jz < mesh->LocalNz; jz++) { - BoutReal jsheath = -(sqrt(mi_me) / (2. * sqrt(PI))) * phisheath; + // Zero-gradient potential + BoutReal const phisheath = phi_fa(r.ind, mesh->yend, jz); + + BoutReal jsheath = (sqrt(mi_me) / (2. * sqrt(PI))) * phisheath; + + // Apply boundary condition half-way between cells + for (int jy = mesh->yend + 1; jy < mesh->LocalNy; jy++) { + // Neumann conditions + P_fa(r.ind, jy, jz) = P_fa(r.ind, mesh->yend, jz); + phi_fa(r.ind, jy, jz) = phisheath; + // Dirichlet condition on Jpar + // WARNING: this is not correct if staggered grids are used + ASSERT3(not mesh->StaggerGrids); + Jpar_fa(r.ind, jy, jz) = 2. * jsheath - Jpar_fa(r.ind, mesh->yend, jz); + } + } + } - // Apply boundary condition half-way between cells - for (int jy = mesh->ystart - 1; jy >= 0; jy--) { - // Neumann conditions - P_fa(r.ind, jy, jz) = P_fa(r.ind, mesh->ystart, jz); - phi_fa(r.ind, jy, jz) = phisheath; - // Dirichlet condition on Jpar - Jpar_fa(r.ind, jy, jz) = 2. * jsheath - Jpar_fa(r.ind, mesh->ystart, jz); - } + // Shift back from field aligned coordinates + phi = fromFieldAligned(phi_fa); + P = fromFieldAligned(P_fa); + Jpar = fromFieldAligned(Jpar_fa); } - } - // At y = yend (upper boundary) + //////////////////////////////////////////////////// + // Parallel electric field - for (RangeIterator r = mesh->iterateBndryUpperY(); !r.isDone(); r++) { - for (int jz = 0; jz < mesh->LocalNz; jz++) { + if (evolve_jpar) { + // Jpar + Field3D B0U = B0 * U; + mesh->communicate(B0U); + ddt(Jpar) = -Grad_parP(B0U, loc) / B0 + eta * Delp2(Jpar); - // Zero-gradient potential - BoutReal const phisheath = phi_fa(r.ind, mesh->yend, jz); + if (relax_j_vac) { + // Make ddt(Jpar) relax to zero. - BoutReal jsheath = (sqrt(mi_me) / (2. * sqrt(PI))) * phisheath; + ddt(Jpar) -= vac_mask * Jpar / relax_j_tconst; + } + } else { + // Vector potential + ddt(Psi) = -Grad_parP(phi * B0, loc) / B0 + eta * Jpar; + + if (eHall) { // electron parallel pressure + ddt(Psi) += 0.25 * delta_i + * (Grad_parP(B0 * P, loc) / B0 + + bracket(interp_to(P0, loc), Psi, bm_mag) * B0); + } + + if (diamag_phi0) { // Equilibrium flow + ddt(Psi) -= bracket(interp_to(phi0, loc), Psi, bm_exb) * B0; + } - // Apply boundary condition half-way between cells - for (int jy = mesh->yend + 1; jy < mesh->LocalNy; jy++) { - // Neumann conditions - P_fa(r.ind, jy, jz) = P_fa(r.ind, mesh->yend, jz); - phi_fa(r.ind, jy, jz) = phisheath; - // Dirichlet condition on Jpar - // WARNING: this is not correct if staggered grids are used - ASSERT3(not mesh->StaggerGrids); - Jpar_fa(r.ind, jy, jz) = 2. * jsheath - Jpar_fa(r.ind, mesh->yend, jz); - } + if (withflow) { // net flow + ddt(Psi) -= V_dot_Grad(V0net, Psi); + } + + if (diamag_grad_t) { // grad_par(T_e) correction + ddt(Psi) += 1.71 * dnorm * 0.5 * Grad_parP(P, loc) / B0; + } + + if (hyperresist > 0.0) { // Hyper-resistivity + ddt(Psi) -= eta * hyperresist * Delp2(Jpar); + } + + if (ehyperviscos > 0.0) { // electron Hyper-viscosity coefficient + ddt(Psi) -= eta * ehyperviscos * Delp2(Jpar2); + } + + // Parallel hyper-viscous diffusion for vector potential + if (diffusion_a4 > 0.0) { + tmpA2 = D2DY2(Psi); + mesh->communicate(tmpA2); + tmpA2.applyBoundary(); + ddt(Psi) -= diffusion_a4 * D2DY2(tmpA2); + } + + // Vacuum solution + if (relax_j_vac) { + // Calculate the J and Psi profile we're aiming for + Field3D Jtarget = Jpar * (1.0 - vac_mask); // Zero in vacuum + + // Invert laplacian for Psi + Field3D Psitarget = aparSolver->solve(Jtarget); + + // Add a relaxation term in the vacuum + ddt(Psi) = + ddt(Psi) * (1. - vac_mask) - (Psi - Psitarget) * vac_mask / relax_j_tconst; + } } - } - // Shift back from field aligned coordinates - phi = fromFieldAligned(phi_fa); - P = fromFieldAligned(P_fa); - Jpar = fromFieldAligned(Jpar_fa); - } + //////////////////////////////////////////////////// + // Vorticity equation + Psi_loc = interp_to(Psi, CELL_CENTRE, "RGN_ALL"); + Psi_loc.applyBoundary(); + // Grad j term + ddt(U) = SQ(B0) * b0xGrad_dot_Grad(Psi_loc, J0, CELL_CENTRE); + if (include_rmp) { + ddt(U) += SQ(B0) * b0xGrad_dot_Grad(rmp_Psi, J0, CELL_CENTRE); + } - //////////////////////////////////////////////////// - // Parallel electric field - - if (evolve_jpar) { - // Jpar - Field3D B0U = B0 * U; - mesh->communicate(B0U); - ddt(Jpar) = -Grad_parP(B0U, loc) / B0 + eta * Delp2(Jpar); - - if (relax_j_vac) { - // Make ddt(Jpar) relax to zero. - - ddt(Jpar) -= vac_mask * Jpar / relax_j_tconst; - } - } else { - // Vector potential - ddt(Psi) = -Grad_parP(phi * B0, loc) / B0 + eta * Jpar; - - if (eHall) { // electron parallel pressure - ddt(Psi) += 0.25 * delta_i - * (Grad_parP(B0 * P, loc) / B0 - + bracket(interp_to(P0, loc), Psi, bm_mag) * B0); - } - - if (diamag_phi0) { // Equilibrium flow - ddt(Psi) -= bracket(interp_to(phi0, loc), Psi, bm_exb) * B0; - } - - if (withflow) { // net flow - ddt(Psi) -= V_dot_Grad(V0net, Psi); - } - - if (diamag_grad_t) { // grad_par(T_e) correction - ddt(Psi) += 1.71 * dnorm * 0.5 * Grad_parP(P, loc) / B0; - } - - if (hyperresist > 0.0) { // Hyper-resistivity - ddt(Psi) -= eta * hyperresist * Delp2(Jpar); - } - - if (ehyperviscos > 0.0) { // electron Hyper-viscosity coefficient - ddt(Psi) -= eta * ehyperviscos * Delp2(Jpar2); - } - - // Parallel hyper-viscous diffusion for vector potential - if (diffusion_a4 > 0.0) { - tmpA2 = D2DY2(Psi); - mesh->communicate(tmpA2); - tmpA2.applyBoundary(); - ddt(Psi) -= diffusion_a4 * D2DY2(tmpA2); - } - - // Vacuum solution - if (relax_j_vac) { - // Calculate the J and Psi profile we're aiming for - Field3D Jtarget = Jpar * (1.0 - vac_mask); // Zero in vacuum - - // Invert laplacian for Psi - Field3D Psitarget = aparSolver->solve(Jtarget); - - // Add a relaxation term in the vacuum - ddt(Psi) = - ddt(Psi) * (1. - vac_mask) - (Psi - Psitarget) * vac_mask / relax_j_tconst; - } - } + ddt(U) += b0xcv * Grad(P); // curvature term - //////////////////////////////////////////////////// - // Vorticity equation - Psi_loc = interp_to(Psi, CELL_CENTRE, "RGN_ALL"); - Psi_loc.applyBoundary(); - // Grad j term - ddt(U) = SQ(B0) * b0xGrad_dot_Grad(Psi_loc, J0, CELL_CENTRE); - if (include_rmp) { - ddt(U) += SQ(B0) * b0xGrad_dot_Grad(rmp_Psi, J0, CELL_CENTRE); - } + if (!nogradparj) { // Parallel current term + ddt(U) -= SQ(B0) * Grad_parP(Jpar, CELL_CENTRE); // b dot grad j + } - ddt(U) += b0xcv * Grad(P); // curvature term + if (withflow && K_H_term) { // K_H_term + ddt(U) -= b0xGrad_dot_Grad(phi, U0); + } - if (!nogradparj) { // Parallel current term - ddt(U) -= SQ(B0) * Grad_parP(Jpar, CELL_CENTRE); // b dot grad j - } + if (diamag_phi0) { // Equilibrium flow + ddt(U) -= b0xGrad_dot_Grad(phi0, U); + } - if (withflow && K_H_term) { // K_H_term - ddt(U) -= b0xGrad_dot_Grad(phi, U0); - } + if (withflow) { // net flow + ddt(U) -= V_dot_Grad(V0net, U); + } - if (diamag_phi0) { // Equilibrium flow - ddt(U) -= b0xGrad_dot_Grad(phi0, U); - } + if (nonlinear) { // Advection + ddt(U) -= bracket(phi, U, bm_exb) * B0; + } - if (withflow) { // net flow - ddt(U) -= V_dot_Grad(V0net, U); - } + // Viscosity terms - if (nonlinear) { // Advection - ddt(U) -= bracket(phi, U, bm_exb) * B0; - } + if (viscos_par > 0.0) { // Parallel viscosity + ddt(U) += viscos_par * Grad2_par2(U); + } - // Viscosity terms + if (diffusion_u4 > 0.0) { + tmpU2 = D2DY2(U); + mesh->communicate(tmpU2); + tmpU2.applyBoundary(); + ddt(U) -= diffusion_u4 * D2DY2(tmpU2); + } - if (viscos_par > 0.0) { // Parallel viscosity - ddt(U) += viscos_par * Grad2_par2(U); - } + if (viscos_perp > 0.0) { + ddt(U) += viscos_perp * Delp2(U); // Perpendicular viscosity + } - if (diffusion_u4 > 0.0) { - tmpU2 = D2DY2(U); - mesh->communicate(tmpU2); - tmpU2.applyBoundary(); - ddt(U) -= diffusion_u4 * D2DY2(tmpU2); - } + // Hyper-viscosity + if (hyperviscos > 0.0) { + // Calculate coefficient. - if (viscos_perp > 0.0) { - ddt(U) += viscos_perp * Delp2(U); // Perpendicular viscosity - } + hyper_mu_x = hyperviscos * metric->g_11() * SQ(metric->dx()) + * abs(metric->g11() * D2DX2(U)) / (abs(U) + 1e-3); + hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries - // Hyper-viscosity - if (hyperviscos > 0.0) { - // Calculate coefficient. + ddt(U) += hyper_mu_x * metric->g11() * D2DX2(U); - hyper_mu_x = hyperviscos * metric->g_11() * SQ(metric->dx()) - * abs(metric->g11() * D2DX2(U)) / (abs(U) + 1e-3); - hyper_mu_x.applyBoundary("dirichlet"); // Set to zero on all boundaries + if (first_run) { // Print out maximum values of viscosity used on this processor + output.write(" Hyper-viscosity values:\n"); + output.write(" Max mu_x = {:e}, Max_DC mu_x = {:e}\n", max(hyper_mu_x), + max(DC(hyper_mu_x))); + } + } - ddt(U) += hyper_mu_x * metric->g11() * D2DX2(U); + if (gyroviscous) { + + Field3D Pi; + Field2D Pi0; + Pi = 0.5 * P; + Pi0 = 0.5 * P0; + + Dperp2Phi0 = Field3D(Delp2(B0 * phi0)); + Dperp2Phi0.applyBoundary(); + mesh->communicate(Dperp2Phi0); + + Dperp2Phi = Delp2(B0 * phi); + Dperp2Phi.applyBoundary(); + mesh->communicate(Dperp2Phi); + + Dperp2Pi0 = Field3D(Delp2(Pi0)); + Dperp2Pi0.applyBoundary(); + mesh->communicate(Dperp2Pi0); + + Dperp2Pi = Delp2(Pi); + Dperp2Pi.applyBoundary(); + mesh->communicate(Dperp2Pi); + + bracketPhi0P = bracket(B0 * phi0, Pi, bm_exb); + bracketPhi0P.applyBoundary(); + mesh->communicate(bracketPhi0P); + + bracketPhiP0 = bracket(B0 * phi, Pi0, bm_exb); + bracketPhiP0.applyBoundary(); + mesh->communicate(bracketPhiP0); + + ddt(U) -= 0.5 * Upara2 * bracket(Pi, Dperp2Phi0, bm_exb) / B0; + ddt(U) -= 0.5 * Upara2 * bracket(Pi0, Dperp2Phi, bm_exb) / B0; + Field3D B0phi = B0 * phi; + mesh->communicate(B0phi); + Field3D B0phi0 = B0 * phi0; + mesh->communicate(B0phi0); + ddt(U) += 0.5 * Upara2 * bracket(B0phi, Dperp2Pi0, bm_exb) / B0; + ddt(U) += 0.5 * Upara2 * bracket(B0phi0, Dperp2Pi, bm_exb) / B0; + ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhi0P) / B0; + ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhiP0) / B0; + + if (nonlinear) { + Field3D B0phi = B0 * phi; + mesh->communicate(B0phi); + bracketPhiP = bracket(B0phi, Pi, bm_exb); + bracketPhiP.applyBoundary(); + mesh->communicate(bracketPhiP); + + ddt(U) -= 0.5 * Upara2 * bracket(Pi, Dperp2Phi, bm_exb) / B0; + ddt(U) += 0.5 * Upara2 * bracket(B0phi, Dperp2Pi, bm_exb) / B0; + ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhiP) / B0; + } + } - if (first_run) { // Print out maximum values of viscosity used on this processor - output.write(" Hyper-viscosity values:\n"); - output.write(" Max mu_x = {:e}, Max_DC mu_x = {:e}\n", max(hyper_mu_x), - max(DC(hyper_mu_x))); - } - } + // left edge sink terms + if (sink_Ul > 0.0) { + ddt(U) -= sink_Ul * sink_tanhxl(P0, U, su_widthl, su_lengthl); // core sink + } - if (gyroviscous) { - - Field3D Pi; - Field2D Pi0; - Pi = 0.5 * P; - Pi0 = 0.5 * P0; - - Dperp2Phi0 = Field3D(Delp2(B0 * phi0)); - Dperp2Phi0.applyBoundary(); - mesh->communicate(Dperp2Phi0); - - Dperp2Phi = Delp2(B0 * phi); - Dperp2Phi.applyBoundary(); - mesh->communicate(Dperp2Phi); - - Dperp2Pi0 = Field3D(Delp2(Pi0)); - Dperp2Pi0.applyBoundary(); - mesh->communicate(Dperp2Pi0); - - Dperp2Pi = Delp2(Pi); - Dperp2Pi.applyBoundary(); - mesh->communicate(Dperp2Pi); - - bracketPhi0P = bracket(B0 * phi0, Pi, bm_exb); - bracketPhi0P.applyBoundary(); - mesh->communicate(bracketPhi0P); - - bracketPhiP0 = bracket(B0 * phi, Pi0, bm_exb); - bracketPhiP0.applyBoundary(); - mesh->communicate(bracketPhiP0); - - ddt(U) -= 0.5 * Upara2 * bracket(Pi, Dperp2Phi0, bm_exb) / B0; - ddt(U) -= 0.5 * Upara2 * bracket(Pi0, Dperp2Phi, bm_exb) / B0; - Field3D B0phi = B0 * phi; - mesh->communicate(B0phi); - Field3D B0phi0 = B0 * phi0; - mesh->communicate(B0phi0); - ddt(U) += 0.5 * Upara2 * bracket(B0phi, Dperp2Pi0, bm_exb) / B0; - ddt(U) += 0.5 * Upara2 * bracket(B0phi0, Dperp2Pi, bm_exb) / B0; - ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhi0P) / B0; - ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhiP0) / B0; - - if (nonlinear) { - Field3D B0phi = B0 * phi; - mesh->communicate(B0phi); - bracketPhiP = bracket(B0phi, Pi, bm_exb); - bracketPhiP.applyBoundary(); - mesh->communicate(bracketPhiP); - - ddt(U) -= 0.5 * Upara2 * bracket(Pi, Dperp2Phi, bm_exb) / B0; - ddt(U) += 0.5 * Upara2 * bracket(B0phi, Dperp2Pi, bm_exb) / B0; - ddt(U) -= 0.5 * Upara2 * Delp2(bracketPhiP) / B0; - } - } + // right edge sink terms + if (sink_Ur > 0.0) { + ddt(U) -= sink_Ur * sink_tanhxr(P0, U, su_widthr, su_lengthr); // sol sink + } - // left edge sink terms - if (sink_Ul > 0.0) { - ddt(U) -= sink_Ul * sink_tanhxl(P0, U, su_widthl, su_lengthl); // core sink - } + //////////////////////////////////////////////////// + // Pressure equation - // right edge sink terms - if (sink_Ur > 0.0) { - ddt(U) -= sink_Ur * sink_tanhxr(P0, U, su_widthr, su_lengthr); // sol sink - } + ddt(P) = 0.0; + if (evolve_pressure) { + ddt(P) -= b0xGrad_dot_Grad(phi, P0); - //////////////////////////////////////////////////// - // Pressure equation + if (diamag_phi0) { // Equilibrium flow + ddt(P) -= b0xGrad_dot_Grad(phi0, P); + } - ddt(P) = 0.0; - if (evolve_pressure) { - ddt(P) -= b0xGrad_dot_Grad(phi, P0); + if (withflow) { // net flow + ddt(P) -= V_dot_Grad(V0net, P); + } - if (diamag_phi0) { // Equilibrium flow - ddt(P) -= b0xGrad_dot_Grad(phi0, P); - } + if (nonlinear) { // Advection + ddt(P) -= bracket(phi, P, bm_exb) * B0; + } + } - if (withflow) { // net flow - ddt(P) -= V_dot_Grad(V0net, P); - } + // Parallel diffusion terms - if (nonlinear) { // Advection - ddt(P) -= bracket(phi, P, bm_exb) * B0; - } - } + if (diffusion_par > 0.0) { // Parallel diffusion + ddt(P) += diffusion_par * Grad2_par2(P); + } - // Parallel diffusion terms + if (diffusion_p4 > 0.0) { // parallel hyper-viscous diffusion for pressure + tmpP2 = D2DY2(P); + mesh->communicate(tmpP2); + tmpP2.applyBoundary(); + ddt(P) = diffusion_p4 * D2DY2(tmpP2); + } - if (diffusion_par > 0.0) { // Parallel diffusion - ddt(P) += diffusion_par * Grad2_par2(P); - } + if (heating_P > 0.0) { // heating source terms + BoutReal pnorm = P0(0, 0); + ddt(P) += heating_P * source_expx2(P0, 2. * hp_width, 0.5 * hp_length) + * (Tbar / pnorm); // heat source + ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11() + * D2DX2(P) * (Tbar / Lbar / Lbar); // radial diffusion + } - if (diffusion_p4 > 0.0) { // parallel hyper-viscous diffusion for pressure - tmpP2 = D2DY2(P); - mesh->communicate(tmpP2); - tmpP2.applyBoundary(); - ddt(P) = diffusion_p4 * D2DY2(tmpP2); - } + if (sink_P > 0.0) { // sink terms + ddt(P) -= sink_P * sink_tanhxr(P0, P, sp_width, sp_length) * Tbar; // sink + } - if (heating_P > 0.0) { // heating source terms - BoutReal pnorm = P0(0, 0); - ddt(P) += heating_P * source_expx2(P0, 2. * hp_width, 0.5 * hp_length) - * (Tbar / pnorm); // heat source - ddt(P) += (100. * source_tanhx(P0, hp_width, hp_length) + 0.01) * metric->g11() - * D2DX2(P) * (Tbar / Lbar / Lbar); // radial diffusion - } + //////////////////////////////////////////////////// + // Compressional effects - if (sink_P > 0.0) { // sink terms - ddt(P) -= sink_P * sink_tanhxr(P0, P, sp_width, sp_length) * Tbar; // sink - } + if (compress) { - //////////////////////////////////////////////////// - // Compressional effects + ddt(P) -= beta * Div_par(Vpar, CELL_CENTRE); - if (compress) { + if (phi_curv) { + ddt(P) -= 2. * beta * b0xcv * Grad(phi); + } - ddt(P) -= beta * Div_par(Vpar, CELL_CENTRE); + // Vpar equation - if (phi_curv) { - ddt(P) -= 2. * beta * b0xcv * Grad(phi); - } + ddt(Vpar) = -0.5 * (Grad_par(P, loc) + Grad_par(P0, loc)); - // Vpar equation + if (nonlinear) { + ddt(Vpar) -= bracket(interp_to(phi, loc), Vpar, bm_exb) * B0; // Advection + } + } - ddt(Vpar) = -0.5 * (Grad_par(P, loc) + Grad_par(P0, loc)); + if (filter_z) { + // Filter out all except filter_z_mode - if (nonlinear) { - ddt(Vpar) -= bracket(interp_to(phi, loc), Vpar, bm_exb) * B0; // Advection - } - } + if (evolve_jpar) { + ddt(Jpar) = filter(ddt(Jpar), filter_z_mode); + } else { + ddt(Psi) = filter(ddt(Psi), filter_z_mode); + } - if (filter_z) { - // Filter out all except filter_z_mode + ddt(U) = filter(ddt(U), filter_z_mode); + ddt(P) = filter(ddt(P), filter_z_mode); + } - if (evolve_jpar) { - ddt(Jpar) = filter(ddt(Jpar), filter_z_mode); - } else { - ddt(Psi) = filter(ddt(Psi), filter_z_mode); - } + if (low_pass_z > 0) { + // Low-pass filter, keeping n up to low_pass_z + if (evolve_jpar) { + ddt(Jpar) = lowPass(ddt(Jpar), low_pass_z, zonal_field); + } else { + ddt(Psi) = lowPass(ddt(Psi), low_pass_z, zonal_field); + } + ddt(U) = lowPass(ddt(U), low_pass_z, zonal_flow); + ddt(P) = lowPass(ddt(P), low_pass_z, zonal_bkgd); + } - ddt(U) = filter(ddt(U), filter_z_mode); - ddt(P) = filter(ddt(P), filter_z_mode); - } + if (damp_width > 0) { + for (int i = 0; i < damp_width; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + if (mesh->firstX()) { + ddt(U)(i, j, k) -= U(i, j, k) / damp_t_const; + } + if (mesh->lastX()) { + ddt(U)(mesh->LocalNx - 1 - i, j, k) -= + U(mesh->LocalNx - 1 - i, j, k) / damp_t_const; + } + } + } + } + } + + first_run = false; - if (low_pass_z > 0) { - // Low-pass filter, keeping n up to low_pass_z - if (evolve_jpar) { - ddt(Jpar) = lowPass(ddt(Jpar), low_pass_z, zonal_field); - } else { - ddt(Psi) = lowPass(ddt(Psi), low_pass_z, zonal_field); - } - ddt(U) = lowPass(ddt(U), low_pass_z, zonal_flow); - ddt(P) = lowPass(ddt(P), low_pass_z, zonal_bkgd); + return 0; } - if (damp_width > 0) { - for (int i = 0; i < damp_width; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { + /******************************************************************************* + * Preconditioner + * + * o System state in variables (as in rhs function) + * o Values to be inverted in time derivatives + * + * o Return values should be in time derivatives + * + * enable by setting solver / use_precon = true in BOUT.inp + *******************************************************************************/ + + int precon(BoutReal UNUSED(t), BoutReal gamma, BoutReal UNUSED(delta)) { + // First matrix, applying L + mesh->communicate(ddt(Psi)); + Field3D Jrhs = Delp2(ddt(Psi)); + Jrhs.applyBoundary("neumann"); + + if (jpar_bndry_width > 0) { + // Boundary in jpar if (mesh->firstX()) { - ddt(U)(i, j, k) -= U(i, j, k) / damp_t_const; + for (int i = jpar_bndry_width; i >= 0; i--) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + Jrhs(i, j, k) = 0.5 * Jrhs(i + 1, j, k); + } + } + } } if (mesh->lastX()) { - ddt(U)(mesh->LocalNx - 1 - i, j, k) -= - U(mesh->LocalNx - 1 - i, j, k) / damp_t_const; + for (int i = mesh->LocalNx - jpar_bndry_width - 1; i < mesh->LocalNx; i++) { + for (int j = 0; j < mesh->LocalNy; j++) { + for (int k = 0; k < mesh->LocalNz; k++) { + Jrhs(i, j, k) = 0.5 * Jrhs(i - 1, j, k); + } + } + } } - } } - } - } - first_run = false; - - return 0; - } - - /******************************************************************************* - * Preconditioner - * - * o System state in variables (as in rhs function) - * o Values to be inverted in time derivatives - * - * o Return values should be in time derivatives - * - * enable by setting solver / use_precon = true in BOUT.inp - *******************************************************************************/ - - int precon(BoutReal UNUSED(t), BoutReal gamma, BoutReal UNUSED(delta)) { - // First matrix, applying L - mesh->communicate(ddt(Psi)); - Field3D Jrhs = Delp2(ddt(Psi)); - Jrhs.applyBoundary("neumann"); - - if (jpar_bndry_width > 0) { - // Boundary in jpar - if (mesh->firstX()) { - for (int i = jpar_bndry_width; i >= 0; i--) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - Jrhs(i, j, k) = 0.5 * Jrhs(i + 1, j, k); - } - } - } - } - if (mesh->lastX()) { - for (int i = mesh->LocalNx - jpar_bndry_width - 1; i < mesh->LocalNx; i++) { - for (int j = 0; j < mesh->LocalNy; j++) { - for (int k = 0; k < mesh->LocalNz; k++) { - Jrhs(i, j, k) = 0.5 * Jrhs(i - 1, j, k); - } - } + mesh->communicate(Jrhs, ddt(P)); + + Field3D U1 = ddt(U); + + auto B0 = tokamak_options.Bxy; + + U1 += (gamma * B0 * B0) * Grad_par(Jrhs, CELL_CENTRE) + (gamma * b0xcv) * Grad(P); + + // Second matrix, solving Alfven wave dynamics + static std::unique_ptr invU{nullptr}; + if (!invU) { + invU = InvertPar::create(); } - } + + invU->setCoefA(1.); + invU->setCoefB(-SQ(gamma) * B0 * B0); + ddt(U) = invU->solve(U1); + ddt(U).applyBoundary(); + + // Third matrix, applying U + Field3D phi3 = phiSolver->solve(ddt(U)); + mesh->communicate(phi3); + phi3.applyBoundary("neumann"); + Field3D B0phi3 = B0 * phi3; + mesh->communicate(B0phi3); + ddt(Psi) = ddt(Psi) - gamma * Grad_par(B0phi3, loc) / B0; + ddt(Psi).applyBoundary(); + + return 0; } - mesh->communicate(Jrhs, ddt(P)); + /******************************************************************************* + * Jacobian-vector multiply + * + * Input + * System state is in (P, Psi, U) + * Vector v is in (F_P, F_Psi, F_U) + * Output + * Jacobian-vector multiplied Jv should be in (P, Psi, U) + * + * NOTE: EXPERIMENTAL + * enable by setting solver / use_jacobian = true in BOUT.inp + *******************************************************************************/ + + int jacobian(BoutReal UNUSED(t)) { + // Communicate + mesh->communicate(ddt(P), ddt(Psi), ddt(U)); + + phi = phiSolver->solve(ddt(U)); + + Jpar = Delp2(ddt(Psi)); - Field3D U1 = ddt(U); - U1 += (gamma * B0 * B0) * Grad_par(Jrhs, CELL_CENTRE) + (gamma * b0xcv) * Grad(P); + mesh->communicate(phi, Jpar); - // Second matrix, solving Alfven wave dynamics - static std::unique_ptr invU{nullptr}; - if (!invU) { - invU = InvertPar::create(); + Field3D JP = -b0xGrad_dot_Grad(phi, P0); + JP.setBoundary("P"); + JP.applyBoundary(); + + auto B0 = tokamak_options.Bxy; + + Field3D B0phi = B0 * phi; + mesh->communicate(B0phi); + Field3D JPsi = -Grad_par(B0phi, loc) / B0; + JPsi.setBoundary("Psi"); + JPsi.applyBoundary(); + + Field3D JU = b0xcv * Grad(ddt(P)) - SQ(B0) * Grad_par(Jpar, CELL_CENTRE) + + SQ(B0) * b0xGrad_dot_Grad(ddt(Psi), J0, CELL_CENTRE); + JU.setBoundary("U"); + JU.applyBoundary(); + + // Put result into time-derivatives + + ddt(P) = JP; + ddt(Psi) = JPsi; + ddt(U) = JU; + + return 0; } - invU->setCoefA(1.); - invU->setCoefB(-SQ(gamma) * B0 * B0); - ddt(U) = invU->solve(U1); - ddt(U).applyBoundary(); - - // Third matrix, applying U - Field3D phi3 = phiSolver->solve(ddt(U)); - mesh->communicate(phi3); - phi3.applyBoundary("neumann"); - Field3D B0phi3 = B0 * phi3; - mesh->communicate(B0phi3); - ddt(Psi) = ddt(Psi) - gamma * Grad_par(B0phi3, loc) / B0; - ddt(Psi).applyBoundary(); - - return 0; - } - - /******************************************************************************* - * Jacobian-vector multiply - * - * Input - * System state is in (P, Psi, U) - * Vector v is in (F_P, F_Psi, F_U) - * Output - * Jacobian-vector multiplied Jv should be in (P, Psi, U) - * - * NOTE: EXPERIMENTAL - * enable by setting solver / use_jacobian = true in BOUT.inp - *******************************************************************************/ - - int jacobian(BoutReal UNUSED(t)) { - // Communicate - mesh->communicate(ddt(P), ddt(Psi), ddt(U)); - - phi = phiSolver->solve(ddt(U)); - - Jpar = Delp2(ddt(Psi)); - - mesh->communicate(phi, Jpar); - - Field3D JP = -b0xGrad_dot_Grad(phi, P0); - JP.setBoundary("P"); - JP.applyBoundary(); - Field3D B0phi = B0 * phi; - mesh->communicate(B0phi); - Field3D JPsi = -Grad_par(B0phi, loc) / B0; - JPsi.setBoundary("Psi"); - JPsi.applyBoundary(); - - Field3D JU = b0xcv * Grad(ddt(P)) - SQ(B0) * Grad_par(Jpar, CELL_CENTRE) - + SQ(B0) * b0xGrad_dot_Grad(ddt(Psi), J0, CELL_CENTRE); - JU.setBoundary("U"); - JU.applyBoundary(); - - // Put result into time-derivatives - - ddt(P) = JP; - ddt(Psi) = JPsi; - ddt(U) = JU; - - return 0; - } - - /******************************************************************************* - * Preconditioner for when phi solved as a constraint - * Currently only possible with the IDA solver - * - * o System state in variables (as in rhs function) - * o Values to be inverted in F_vars - * - * o Return values should be in vars (overwriting system state) - *******************************************************************************/ - - int precon_phi(BoutReal UNUSED(t), BoutReal UNUSED(cj), BoutReal UNUSED(delta)) { - ddt(phi) = phiSolver->solve(C_phi - ddt(U)); - return 0; - } + /******************************************************************************* + * Preconditioner for when phi solved as a constraint + * Currently only possible with the IDA solver + * + * o System state in variables (as in rhs function) + * o Values to be inverted in F_vars + * + * o Return values should be in vars (overwriting system state) + *******************************************************************************/ + + int precon_phi(BoutReal UNUSED(t), BoutReal UNUSED(cj), BoutReal UNUSED(delta)) { + ddt(phi) = phiSolver->solve(C_phi - ddt(U)); + return 0; + } }; BOUTMAIN(ELMpb); diff --git a/examples/gyro-gem/gem.cxx b/examples/gyro-gem/gem.cxx index cad585beb4..3eb7f6fcca 100644 --- a/examples/gyro-gem/gem.cxx +++ b/examples/gyro-gem/gem.cxx @@ -10,7 +10,7 @@ ****************************************************************/ #include -#include +#include #include #include @@ -182,17 +182,6 @@ class GEM : public PhysicsModel { flat_temp = options["flat_temp"].withDefault(-1.0); flat_dens = options["flat_dens"].withDefault(-1.0); - ////////////////////////////////// - // Read profiles - - // Mesh - Field2D Rxy, Bpxy, Btxy, Bxy, hthe; - GRID_LOAD(Rxy); // Major radius [m] - GRID_LOAD(Bpxy); // Poloidal B field [T] - GRID_LOAD(Btxy); // Toroidal B field [T] - GRID_LOAD(Bxy); // Total B field [T] - GRID_LOAD(hthe); // Poloidal arc length [m / radian] - GRID_LOAD(Te0); // Electron temperature in eV GRID_LOAD(Ni0); // Ion number density in 10^20 m^-3 @@ -253,14 +242,18 @@ class GEM : public PhysicsModel { Tbar = options["Tbar"].withDefault(Tbar); // Override in options file SAVE_ONCE(Tbar); // Timescale in seconds + auto tokamak_options = bout::TokamakOptions(*mesh); + if (mesh->get(Bbar, "Bbar")) { if (mesh->get(Bbar, "bmag")) { - Bbar = max(Bxy, true); + Bbar = max(tokamak_options.Bxy, true); } } Bbar = options["Bbar"].withDefault(Bbar); // Override in options file SAVE_ONCE(Bbar); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lbar, Bbar); + beta_e = 4.e-7 * PI * max(p_e, true) / (Bbar * Bbar); SAVE_ONCE(beta_e); @@ -352,48 +345,12 @@ class GEM : public PhysicsModel { output << "\tNormalised rho_e = " << rho_e << endl; output << "\tNormalised rho_i = " << rho_i << endl; - ////////////////////////////////// - // Metric tensor components - coord = mesh->getCoordinates(); - - // Normalise - hthe /= Lbar; // parallel derivatives normalised to Lperp - - Bpxy /= Bbar; - Btxy /= Bbar; - Bxy /= Bbar; - - Rxy /= rho_s; // Perpendicular derivatives normalised to rho_s - coord->setDx(coord->dx() / (rho_s * rho_s * Bbar)); - - // Metric components - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(Bxy) / g11; - const auto g12 = 0.0; - const auto g13 = 0.; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11; - const auto g_22 = SQ(Bxy * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = 0.; - const auto g_13 = 0.; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; - - coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coord->setJ(hthe / Bpxy); - coord->setBxy(Bxy); - // Set B field vector B0vec.covariant = false; B0vec.x = 0.; - B0vec.y = Bpxy / hthe; + B0vec.y = tokamak_options.Bpxy / tokamak_options.hthe; B0vec.z = 0.; // Precompute this for use in RHS diff --git a/examples/laplacexy/alfven-wave/alfven.cxx b/examples/laplacexy/alfven-wave/alfven.cxx index a4894e2cb5..7cca9bc171 100644 --- a/examples/laplacexy/alfven-wave/alfven.cxx +++ b/examples/laplacexy/alfven-wave/alfven.cxx @@ -4,6 +4,7 @@ #include #include #include +#include /// Fundamental constants const BoutReal PI = 3.14159265; @@ -169,57 +170,10 @@ class Alfven : public PhysicsModel { } void LoadMetric(BoutReal Lnorm, BoutReal Bnorm) { - // Load metric coefficients from the mesh - Field2D Rxy, Bpxy, Btxy, hthe, sinty; - GRID_LOAD5(Rxy, Bpxy, Btxy, hthe, sinty); // Load metrics + auto tokamak_options = bout::TokamakOptions(*mesh); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lnorm, Bnorm); Coordinates* coord = mesh->getCoordinates(); // Metric tensor - - // Checking for dpsi and qinty used in BOUT grids - Field2D dx; - if (!mesh->get(dx, "dpsi")) { - output << "\tUsing dpsi as the x grid spacing\n"; - coord->setDx(dx); // Only use dpsi if found - } else { - // dx will have been read already from the grid - output << "\tUsing dx as the x grid spacing\n"; - } - - Rxy /= Lnorm; - hthe /= Lnorm; - sinty *= SQ(Lnorm) * Bnorm; - coord->setDx(coord->dx() / (SQ(Lnorm) * Bnorm)); - - Bpxy /= Bnorm; - Btxy /= Bnorm; - coord->setBxy(coord->Bxy() / Bnorm); - - // Calculate metric components - sinty = 0.0; // I disappears from metric for shifted coordinates - - BoutReal sbp = 1.0; // Sign of Bp - if (min(Bpxy, true) < 0.0) { - sbp = -1.0; - } - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(sinty) * g11 + SQ(coord->Bxy()) / g11; - const auto g12 = 0.0; - const auto g13 = -sinty * g11; - const auto g23 = -sbp * Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + SQ(sinty * Rxy); - const auto g_22 = SQ(coord->Bxy() * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = sbp * Btxy * hthe * sinty * Rxy / Bpxy; - const auto g_13 = sinty * Rxy * Rxy; - const auto g_23 = sbp * Btxy * hthe * Rxy / Bpxy; - - coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coord->setJ(hthe / Bpxy); } }; diff --git a/examples/laplacexy/laplace_perp/test.cxx b/examples/laplacexy/laplace_perp/test.cxx index 7b98f85ef5..87d4b5ecb0 100644 --- a/examples/laplacexy/laplace_perp/test.cxx +++ b/examples/laplacexy/laplace_perp/test.cxx @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -7,44 +7,18 @@ using bout::globals::mesh; int main(int argc, char** argv) { + BoutInitialise(argc, argv); /////////////////////////////////////// bool calc_metric; calc_metric = Options::root()["calc_metric"].withDefault(false); if (calc_metric) { - // Read metric tensor - Field2D Rxy, Btxy, Bpxy, B0, hthe, I; - mesh->get(Rxy, "Rxy"); // m - mesh->get(Btxy, "Btxy"); // T - mesh->get(Bpxy, "Bpxy"); // T - mesh->get(B0, "Bxy"); // T - mesh->get(hthe, "hthe"); // m - mesh->get(I, "sinty"); // m^-2 T^-1 + auto tokamak_options = bout::TokamakOptions(*mesh); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, 1.0, 1.0); + } Coordinates* coord = mesh->getCoordinates(); - - // Calculate metrics - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(I) * g11 + SQ(B0) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + SQ(I * Rxy); - const auto g_22 = SQ(B0 * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; - - coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coord->setJ(hthe / Bpxy); - coord->setBxy(B0); - } /////////////////////////////////////// // Read an analytic input diff --git a/examples/wave-slab/wave_slab.cxx b/examples/wave-slab/wave_slab.cxx index 0bf4f1a9e7..ff2d9b821f 100644 --- a/examples/wave-slab/wave_slab.cxx +++ b/examples/wave-slab/wave_slab.cxx @@ -11,44 +11,24 @@ */ #include +#include class WaveTest : public PhysicsModel { public: int init(bool UNUSED(restarting)) { - auto* coords = mesh->getCoordinates(); - Field2D Rxy, Bpxy, Btxy, hthe, I; - GRID_LOAD(Rxy); - GRID_LOAD(Bpxy); - GRID_LOAD(Btxy); - GRID_LOAD(hthe); - coords->setBxy(mesh->get("Bxy")); + + auto tokamak_options = bout::TokamakOptions(*mesh); + int ShiftXderivs = 0; mesh->get(ShiftXderivs, "false"); + BoutReal shearFactor = 1.0; if (ShiftXderivs) { // No integrated shear in metric - I = 0.0; - } else { - mesh->get(I, "sinty"); + shearFactor = 0.0; } - - const auto g11 = pow(Rxy * Bpxy, 2.0); - const auto g22 = 1.0 / pow(hthe, 2.0); - const auto g33 = pow(I, 2.0) * g11 + pow(coords->Bxy(), 2.0) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + (pow(I * Rxy, 2.0)); - const auto g_22 = pow(coords->Bxy() * hthe / Bpxy, 2.0); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; - - coords->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coords->setJ(hthe / Bpxy); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, 1.0, 1.0, shearFactor); + + auto* coords = mesh->getCoordinates(); solver->add(f, "f"); solver->add(g, "g"); diff --git a/include/bout/coordinates.hxx b/include/bout/coordinates.hxx index 954f65d272..4ae3ac0740 100644 --- a/include/bout/coordinates.hxx +++ b/include/bout/coordinates.hxx @@ -500,9 +500,9 @@ private: /* /// Standard coordinate system for tokamak simulations -class TokamakCoordinates : public Coordinates { +class TokamakOptions : public Coordinates { public: - TokamakCoordinates(Mesh *mesh) : Coordinates(mesh) { + TokamakOptions(Mesh *mesh) : Coordinates(mesh) { } private: diff --git a/include/bout/tokamak_coordinates.hxx b/include/bout/tokamak_coordinates.hxx new file mode 100644 index 0000000000..f13e718787 --- /dev/null +++ b/include/bout/tokamak_coordinates.hxx @@ -0,0 +1,35 @@ + +#ifndef BOUT_TOKAMAK_COORDINATES_HXX +#define BOUT_TOKAMAK_COORDINATES_HXX + +#include "bout.hxx" +#include "bout/bout_types.hxx" +#include "bout/field2d.hxx" +#include "bout/metric_tensor.hxx" + +using FieldMetric = MetricTensor::FieldMetric; + +namespace bout { + + struct TokamakOptions { + Field2D Rxy; + Field2D Bpxy; + Field2D Btxy; + Field2D Bxy; + Field2D hthe; + FieldMetric I; + FieldMetric dx; + + TokamakOptions(Mesh &mesh); + + void normalise(BoutReal Lbar, BoutReal Bbar, BoutReal ShearFactor); + }; + + BoutReal get_sign_of_bp(const Field2D &Bpxy); + + void set_tokamak_coordinates_on_mesh(TokamakOptions &tokamak_options, Mesh &mesh, BoutReal Lbar, + BoutReal Bbar, BoutReal ShearFactor = 0.0); + +} + +#endif //BOUT_TOKAMAK_COORDINATES_HXX diff --git a/src/mesh/tokamak_coordinates.cxx b/src/mesh/tokamak_coordinates.cxx new file mode 100644 index 0000000000..e5a7636b7d --- /dev/null +++ b/src/mesh/tokamak_coordinates.cxx @@ -0,0 +1,78 @@ + +#include "bout/tokamak_coordinates.hxx" +#include "bout/bout.hxx" +#include "bout/bout_types.hxx" +#include "bout/field2d.hxx" +#include "bout/utils.hxx" + + +namespace bout { + + BoutReal get_sign_of_bp(const Field2D &Bpxy) { + if (min(Bpxy, true) < 0.0) { + return -1.0; + } + return 1.0; + } + + TokamakOptions::TokamakOptions(Mesh &mesh) { + mesh.get(Rxy, "Rxy"); + // mesh->get(Zxy, "Zxy"); + mesh.get(Bpxy, "Bpxy"); + mesh.get(Btxy, "Btxy"); + mesh.get(Bxy, "Bxy"); + mesh.get(hthe, "hthe"); + mesh.get(I, "sinty"); + if (mesh.get(dx, "dpsi")) { + dx = mesh.getCoordinates()->dx(); + } + } + + void TokamakOptions::normalise(BoutReal Lbar, BoutReal Bbar, BoutReal ShearFactor) { + Rxy /= Lbar; + Bpxy /= Bbar; + Btxy /= Bbar; + Bxy /= Bbar; + hthe /= Lbar; + I *= Lbar * Lbar * Bbar * ShearFactor; + dx /= Lbar * Lbar * Bbar; + } + + void set_tokamak_coordinates_on_mesh(TokamakOptions &tokamak_options, Mesh &mesh, BoutReal Lbar, + BoutReal + Bbar, BoutReal + ShearFactor) { + + tokamak_options.normalise(Lbar, Bbar, ShearFactor); + + const BoutReal sign_of_bp = get_sign_of_bp(tokamak_options.Bpxy); + + auto *coord = mesh.getCoordinates(); + + const auto g11 = SQ(tokamak_options.Rxy * tokamak_options.Bpxy); + const auto g22 = 1.0 / SQ(tokamak_options.hthe); + const auto g33 = SQ(ShearFactor) * g11 + SQ(tokamak_options.Bxy) / g11; + const auto g12 = 0.0; + const auto g13 = -ShearFactor * g11; + const auto g23 = -sign_of_bp * tokamak_options.Btxy / + (tokamak_options.hthe * tokamak_options.Bpxy * tokamak_options.Rxy); + + const auto g_11 = 1.0 / g11 + SQ(ShearFactor * tokamak_options.Rxy); + const auto g_22 = SQ(tokamak_options.Bxy * tokamak_options.hthe / tokamak_options.Bpxy); + const auto g_33 = tokamak_options.Rxy * tokamak_options.Rxy; + const auto g_12 = + sign_of_bp * tokamak_options.Btxy * tokamak_options.hthe * ShearFactor * tokamak_options.Rxy / + tokamak_options.Bpxy; + const auto g_13 = ShearFactor * tokamak_options.Rxy * tokamak_options.Rxy; + const auto g_23 = sign_of_bp * tokamak_options.Btxy * tokamak_options.hthe * tokamak_options.Rxy / + tokamak_options.Bpxy; + + coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), + CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); + + + coord->setJ(tokamak_options.hthe / tokamak_options.Bpxy); + coord->setBxy(tokamak_options.Bxy); + coord->setDx(tokamak_options.dx); + } +} \ No newline at end of file diff --git a/tests/MMS/GBS/gbs.cxx b/tests/MMS/GBS/gbs.cxx index 9fed76c119..5ea46173c6 100644 --- a/tests/MMS/GBS/gbs.cxx +++ b/tests/MMS/GBS/gbs.cxx @@ -17,535 +17,492 @@ #include #include #include +#include int GBS::init(bool restarting) { - Options* opt = Options::getRoot(); - coords = mesh->getCoordinates(); - - // Switches in model section - Options* optgbs = opt->getSection("GBS"); - OPTION(optgbs, ionvis, false); - if (ionvis) { - OPTION(optgbs, Ti, 10); // Ion temperature [eV] - } - OPTION(optgbs, elecvis, false); // Include electron viscosity? - OPTION(optgbs, resistivity, true); // Include resistivity? - OPTION(optgbs, estatic, true); // Electrostatic? - - // option for ExB Poisson Bracket - int bm_exb_flag; - OPTION(optgbs, bm_exb_flag, 2); // Arakawa default - switch (bm_exb_flag) { - case 0: { - bm_exb = BRACKET_STD; - output << "\tBrackets for ExB: default differencing\n"; - break; - } - case 1: { - bm_exb = BRACKET_SIMPLE; - output << "\tBrackets for ExB: simplified operator\n"; - break; - } - case 2: { - bm_exb = BRACKET_ARAKAWA; - output << "\tBrackets for ExB: Arakawa scheme\n"; - break; - } - case 3: { - bm_exb = BRACKET_CTU; - output << "\tBrackets for ExB: Corner Transport Upwind method\n"; - break; - } - default: - output << "ERROR: Invalid choice of bracket method. Must be 0 - 3\n"; - return 1; - } - - OPTION(opt->getSection("solver"), mms, false); - - if (ionvis) { - SAVE_REPEAT(Gi); - } - if (elecvis) { - SAVE_REPEAT(Ge); - } - if (resistivity) { - SAVE_REPEAT(nu); - } - - // Normalisation - OPTION(optgbs, Tnorm, 100); // Reference temperature [eV] - OPTION(optgbs, Nnorm, 1e19); // Reference density [m^-3] - OPTION(optgbs, Bnorm, 1.0); // Reference magnetic field [T] - OPTION(optgbs, AA, 2.0); // Ion mass - - output.write("Normalisation Te={:e}, Ne={:e}, B={:e}\n", Tnorm, Nnorm, Bnorm); - SAVE_ONCE4(Tnorm, Nnorm, Bnorm, AA); // Save - - Cs0 = sqrt(SI::qe * Tnorm / (AA * SI::Mp)); // Reference sound speed [m/s] - Omega_ci = SI::qe * Bnorm / (AA * SI::Mp); // Ion cyclotron frequency [1/s] - rho_s0 = Cs0 / Omega_ci; - - mi_me = AA * SI::Mp / SI::Me; - beta_e = SI::qe * Tnorm * Nnorm / (SQ(Bnorm) / SI::mu0); - - output.write("\tmi_me={:e}, beta_e={:e}\n", mi_me, beta_e); - SAVE_ONCE2(mi_me, beta_e); - - output.write("\t Cs={:e}, rho_s={:e}, Omega_ci={:e}\n", Cs0, rho_s0, Omega_ci); - SAVE_ONCE3(Cs0, rho_s0, Omega_ci); - - // Collision times - BoutReal Coulomb = 6.6 - 0.5 * log(Nnorm * 1e-20) + 1.5 * log(Tnorm); - tau_e0 = 1. / (2.91e-6 * (Nnorm / 1e6) * Coulomb * pow(Tnorm, -3. / 2)); - tau_i0 = sqrt(AA) / (4.80e-8 * (Nnorm / 1e6) * Coulomb * pow(Tnorm, -3. / 2)); - - output.write("\ttau_e0={:e}, tau_i0={:e}\n", tau_e0, tau_i0); - - // Get switches from each variable section - Options* optne = opt->getSection("Ne"); - optne->get("evolve", evolve_Ne, true); - optne->get("D", Dn, 1e-3); - optne->get("H", Hn, -1.0); - if (mms) { - Sn = 0.0; - } else { - std::string source; - optne->get("source", source, "0.0"); - Sn = FieldFactory::get()->create3D(source, NULL, mesh); - Sn /= Omega_ci; - SAVE_ONCE(Sn); - } - - Options* optte = opt->getSection("Te"); - optte->get("evolve", evolve_Te, true); - optte->get("D", Dte, 1e-3); - optte->get("H", Hte, -1.0); - if (mms) { - Sp = 0.0; - } else { - std::string source; - optte->get("source", source, "0.0"); - Sp = FieldFactory::get()->create3D(source, NULL, mesh); - Sp /= Omega_ci; - SAVE_ONCE(Sp); - } - - Options* optvort = opt->getSection("Vort"); - optvort->get("evolve", evolve_Vort, true); - optvort->get("D", Dvort, 1e-3); - optvort->get("H", Hvort, -1.0); - - Options* optve = opt->getSection("VePsi"); - optve->get("evolve", evolve_Ve, true); - optve->get("D", Dve, 1e-3); - optve->get("H", Hve, -1.0); - - Options* optvi = opt->getSection("Vi"); - optvi->get("evolve", evolve_Vi, true); - optvi->get("D", Dvi, 1e-3); - optvi->get("H", Hvi, -1.0); - - OPTION(optgbs, parallel, true); - if (!parallel) { - // No parallel dynamics - evolve_Ve = false; - evolve_Vi = false; - } - - // If evolving, add to solver. Otherwise, set to an initial (fixed) value. - if (evolve_Ne) { - solver->add(Ne, "Ne"); - evars.add(Ne); - } else { - initial_profile("Ne", Ne); - SAVE_ONCE(Ne); - } - if (evolve_Vort) { - solver->add(Vort, "Vort"); - evars.add(Vort); - } else { - initial_profile("Vort", Vort); - SAVE_ONCE(Vort); - } - if (evolve_Ve) { - solver->add(VePsi, "VePsi"); - evars.add(VePsi); - } else { - initial_profile("VePsi", VePsi); - SAVE_ONCE(VePsi); - } - if (evolve_Vi) { - solver->add(Vi, "Vi"); - evars.add(Vi); - } else { - initial_profile("Vi", Vi); - SAVE_ONCE(Vi); - } - if (evolve_Te) { - solver->add(Te, "Te"); - evars.add(Te); - } else { - initial_profile("Te", Te); - SAVE_ONCE(Te); - } - - if (evolve_Ve && (!estatic)) { - SAVE_REPEAT2(psi, Ve); - } - - // Load metric tensor from the mesh, passing length and B field normalisations - LoadMetric(rho_s0, Bnorm); - - if (!restarting) { - bool startprofiles; - OPTION(optgbs, startprofiles, true); - if (startprofiles) { - // Read profiles from the mesh - Field2D NeMesh; - if (mesh->get(NeMesh, "Ne0")) { - output << "\nHERE\n"; - // No Ne0. Try Ni0 - if (mesh->get(NeMesh, "Ni0")) { - output << "WARNING: Neither Ne0 nor Ni0 found in mesh input\n"; + Options *opt = Options::getRoot(); + coords = mesh->getCoordinates(); + + // Switches in model section + Options *optgbs = opt->getSection("GBS"); + OPTION(optgbs, ionvis, false); + if (ionvis) { + OPTION(optgbs, Ti, 10); // Ion temperature [eV] + } + OPTION(optgbs, elecvis, false); // Include electron viscosity? + OPTION(optgbs, resistivity, true); // Include resistivity? + OPTION(optgbs, estatic, true); // Electrostatic? + + // option for ExB Poisson Bracket + int bm_exb_flag; + OPTION(optgbs, bm_exb_flag, 2); // Arakawa default + switch (bm_exb_flag) { + case 0: { + bm_exb = BRACKET_STD; + output << "\tBrackets for ExB: default differencing\n"; + break; + } + case 1: { + bm_exb = BRACKET_SIMPLE; + output << "\tBrackets for ExB: simplified operator\n"; + break; + } + case 2: { + bm_exb = BRACKET_ARAKAWA; + output << "\tBrackets for ExB: Arakawa scheme\n"; + break; } - } - NeMesh *= 1e20; // Convert to m^-3 - - Field2D TeMesh; - if (mesh->get(TeMesh, "Te0")) { - // No Te0 - output << "WARNING: Te0 not found in mesh\n"; - } - Field3D ViMesh; - mesh->get(ViMesh, "Vi0"); - - // Normalise - NeMesh /= Nnorm; - TeMesh /= Tnorm; - ViMesh /= Cs0; - - // Add profiles in the mesh file - Ne += NeMesh; - Te += TeMesh; - Vi += ViMesh; + case 3: { + bm_exb = BRACKET_CTU; + output << "\tBrackets for ExB: Corner Transport Upwind method\n"; + break; + } + default: + output << "ERROR: Invalid choice of bracket method. Must be 0 - 3\n"; + return 1; } - // Check for negatives - if (min(Ne, true) < 0.0) { - throw BoutException("Starting density is negative"); + OPTION(opt->getSection("solver"), mms, false); + + if (ionvis) { + SAVE_REPEAT(Gi); } - if (max(Ne, true) < 1e-5) { - throw BoutException("Starting density is too small"); + if (elecvis) { + SAVE_REPEAT(Ge); } - if (min(Te, true) < 0.0) { - throw BoutException("Starting temperature is negative"); + if (resistivity) { + SAVE_REPEAT(nu); } - if (max(Te, true) < 1e-5) { - throw BoutException("Starting temperature is too small"); + + // Normalisation + OPTION(optgbs, Tnorm, 100); // Reference temperature [eV] + OPTION(optgbs, Nnorm, 1e19); // Reference density [m^-3] + OPTION(optgbs, Bnorm, 1.0); // Reference magnetic field [T] + OPTION(optgbs, AA, 2.0); // Ion mass + + output.write("Normalisation Te={:e}, Ne={:e}, B={:e}\n", Tnorm, Nnorm, Bnorm); + SAVE_ONCE4(Tnorm, Nnorm, Bnorm, AA); // Save + + Cs0 = sqrt(SI::qe * Tnorm / (AA * SI::Mp)); // Reference sound speed [m/s] + Omega_ci = SI::qe * Bnorm / (AA * SI::Mp); // Ion cyclotron frequency [1/s] + rho_s0 = Cs0 / Omega_ci; + + mi_me = AA * SI::Mp / SI::Me; + beta_e = SI::qe * Tnorm * Nnorm / (SQ(Bnorm) / SI::mu0); + + output.write("\tmi_me={:e}, beta_e={:e}\n", mi_me, beta_e); + SAVE_ONCE2(mi_me, beta_e); + + output.write("\t Cs={:e}, rho_s={:e}, Omega_ci={:e}\n", Cs0, rho_s0, Omega_ci); + SAVE_ONCE3(Cs0, rho_s0, Omega_ci); + + // Collision times + BoutReal Coulomb = 6.6 - 0.5 * log(Nnorm * 1e-20) + 1.5 * log(Tnorm); + tau_e0 = 1. / (2.91e-6 * (Nnorm / 1e6) * Coulomb * pow(Tnorm, -3. / 2)); + tau_i0 = sqrt(AA) / (4.80e-8 * (Nnorm / 1e6) * Coulomb * pow(Tnorm, -3. / 2)); + + output.write("\ttau_e0={:e}, tau_i0={:e}\n", tau_e0, tau_i0); + + // Get switches from each variable section + Options *optne = opt->getSection("Ne"); + optne->get("evolve", evolve_Ne, true); + optne->get("D", Dn, 1e-3); + optne->get("H", Hn, -1.0); + if (mms) { + Sn = 0.0; + } else { + std::string source; + optne->get("source", source, "0.0"); + Sn = FieldFactory::get()->create3D(source, NULL, mesh); + Sn /= Omega_ci; + SAVE_ONCE(Sn); } - } - SAVE_REPEAT(phi); + Options *optte = opt->getSection("Te"); + optte->get("evolve", evolve_Te, true); + optte->get("D", Dte, 1e-3); + optte->get("H", Hte, -1.0); + if (mms) { + Sp = 0.0; + } else { + std::string source; + optte->get("source", source, "0.0"); + Sp = FieldFactory::get()->create3D(source, NULL, mesh); + Sp /= Omega_ci; + SAVE_ONCE(Sp); + } - phi.setBoundary("phi"); // For Y boundaries (if any) + Options *optvort = opt->getSection("Vort"); + optvort->get("evolve", evolve_Vort, true); + optvort->get("D", Dvort, 1e-3); + optvort->get("H", Hvort, -1.0); + + Options *optve = opt->getSection("VePsi"); + optve->get("evolve", evolve_Ve, true); + optve->get("D", Dve, 1e-3); + optve->get("H", Hve, -1.0); + + Options *optvi = opt->getSection("Vi"); + optvi->get("evolve", evolve_Vi, true); + optvi->get("D", Dvi, 1e-3); + optvi->get("H", Hvi, -1.0); + + OPTION(optgbs, parallel, true); + if (!parallel) { + // No parallel dynamics + evolve_Ve = false; + evolve_Vi = false; + } - // Curvature - OPTION(optgbs, curv_method, 1); // Get the method to use + // If evolving, add to solver. Otherwise, set to an initial (fixed) value. + if (evolve_Ne) { + solver->add(Ne, "Ne"); + evars.add(Ne); + } else { + initial_profile("Ne", Ne); + SAVE_ONCE(Ne); + } + if (evolve_Vort) { + solver->add(Vort, "Vort"); + evars.add(Vort); + } else { + initial_profile("Vort", Vort); + SAVE_ONCE(Vort); + } + if (evolve_Ve) { + solver->add(VePsi, "VePsi"); + evars.add(VePsi); + } else { + initial_profile("VePsi", VePsi); + SAVE_ONCE(VePsi); + } + if (evolve_Vi) { + solver->add(Vi, "Vi"); + evars.add(Vi); + } else { + initial_profile("Vi", Vi); + SAVE_ONCE(Vi); + } + if (evolve_Te) { + solver->add(Te, "Te"); + evars.add(Te); + } else { + initial_profile("Te", Te); + SAVE_ONCE(Te); + } - switch (curv_method) { - case 0: // bxcv vector, upwinding - case 1: { // bxcv vector, central differencing - bxcv.covariant = false; // Read contravariant components - GRID_LOAD(bxcv); // Specified components of b0 x kappa + if (evolve_Ve && (!estatic)) { + SAVE_REPEAT2(psi, Ve); + } - bool ShiftXderivs; - Options::getRoot()->get("shiftXderivs", ShiftXderivs, false); // Read global flag - if (ShiftXderivs) { - Field2D sinty; - GRID_LOAD(sinty); - bxcv.z += sinty * bxcv.x; + // Load metric tensor from the mesh, passing length and B field normalisations + LoadMetric(rho_s0, Bnorm); + + if (!restarting) { + bool startprofiles; + OPTION(optgbs, startprofiles, true); + if (startprofiles) { + // Read profiles from the mesh + Field2D NeMesh; + if (mesh->get(NeMesh, "Ne0")) { + output << "\nHERE\n"; + // No Ne0. Try Ni0 + if (mesh->get(NeMesh, "Ni0")) { + output << "WARNING: Neither Ne0 nor Ni0 found in mesh input\n"; + } + } + NeMesh *= 1e20; // Convert to m^-3 + + Field2D TeMesh; + if (mesh->get(TeMesh, "Te0")) { + // No Te0 + output << "WARNING: Te0 not found in mesh\n"; + } + Field3D ViMesh; + mesh->get(ViMesh, "Vi0"); + + // Normalise + NeMesh /= Nnorm; + TeMesh /= Tnorm; + ViMesh /= Cs0; + + // Add profiles in the mesh file + Ne += NeMesh; + Te += TeMesh; + Vi += ViMesh; + } + + // Check for negatives + if (min(Ne, true) < 0.0) { + throw BoutException("Starting density is negative"); + } + if (max(Ne, true) < 1e-5) { + throw BoutException("Starting density is too small"); + } + if (min(Te, true) < 0.0) { + throw BoutException("Starting temperature is negative"); + } + if (max(Te, true) < 1e-5) { + throw BoutException("Starting temperature is too small"); + } } - // Normalise curvature - bxcv.x /= Bnorm; - bxcv.y /= rho_s0 * rho_s0; - bxcv.z *= rho_s0 * rho_s0; - break; - } - case 2: { // logB, read from input - GRID_LOAD(logB); - mesh->communicate(logB); - SAVE_ONCE(logB); - break; - } - case 3: { // logB, taken from mesh - logB = log(coords->Bxy()); - break; - } - default: - throw BoutException("Invalid value for curv_method"); - }; - - // Phi solver - phiSolver = Laplacian::create(opt->getSection("phiSolver")); - aparSolver = Laplacian::create(opt->getSection("aparSolver")); - - dx4 = SQ(SQ(coords->dx())); - dy4 = SQ(SQ(coords->dy())); - dz4 = SQ(SQ(coords->dz())); - - SAVE_REPEAT(Ve); - - output.write("dx = {:e}, dy = {:e}, dz = {:e}\n", (coords->dx())(2, 2), - (coords->dy())(2, 2), coords->dz()); - output.write("g11 = {:e}, g22 = {:e}, g33 = {:e}\n", coords->g11(2, 2), - coords->g22(2, 2), coords->g33(2, 2)); - output.write("g12 = {:e}, g23 = {:e}\n", coords->g12(2, 2), coords->g23(2, 2)); - output.write("g_11 = {:e}, g_22 = {:e}, g_33 = {:e}\n", coords->g_11(2, 2), - coords->g_22(2, 2), coords->g_33(2, 2)); - output.write("g_12 = {:e}, g_23 = {:e}\n", coords->g_12(2, 2), coords->g_23(2, 2)); - - std::shared_ptr gen = - FieldFactory::get()->parse("source", Options::getRoot()->getSection("ne")); - output << "Ne::source = " << gen->str() << endl; - - return 0; + + SAVE_REPEAT(phi); + + phi.setBoundary("phi"); // For Y boundaries (if any) + + // Curvature + OPTION(optgbs, curv_method, 1); // Get the method to use + + switch (curv_method) { + case 0: // bxcv vector, upwinding + case 1: { // bxcv vector, central differencing + bxcv.covariant = false; // Read contravariant components + GRID_LOAD(bxcv); // Specified components of b0 x kappa + + bool ShiftXderivs; + Options::getRoot()->get("shiftXderivs", ShiftXderivs, false); // Read global flag + if (ShiftXderivs) { + Field2D sinty; + GRID_LOAD(sinty); + bxcv.z += sinty * bxcv.x; + } + // Normalise curvature + bxcv.x /= Bnorm; + bxcv.y /= rho_s0 * rho_s0; + bxcv.z *= rho_s0 * rho_s0; + break; + } + case 2: { // logB, read from input + GRID_LOAD(logB); + mesh->communicate(logB); + SAVE_ONCE(logB); + break; + } + case 3: { // logB, taken from mesh + logB = log(coords->Bxy()); + break; + } + default: + throw BoutException("Invalid value for curv_method"); + }; + + // Phi solver + phiSolver = Laplacian::create(opt->getSection("phiSolver")); + aparSolver = Laplacian::create(opt->getSection("aparSolver")); + + dx4 = SQ(SQ(coords->dx())); + dy4 = SQ(SQ(coords->dy())); + dz4 = SQ(SQ(coords->dz())); + + SAVE_REPEAT(Ve); + + output.write("dx = {:e}, dy = {:e}, dz = {:e}\n", (coords->dx())(2, 2), + (coords->dy())(2, 2), coords->dz()); + output.write("g11 = {:e}, g22 = {:e}, g33 = {:e}\n", coords->g11(2, 2), + coords->g22(2, 2), coords->g33(2, 2)); + output.write("g12 = {:e}, g23 = {:e}\n", coords->g12(2, 2), coords->g23(2, 2)); + output.write("g_11 = {:e}, g_22 = {:e}, g_33 = {:e}\n", coords->g_11(2, 2), + coords->g_22(2, 2), coords->g_33(2, 2)); + output.write("g_12 = {:e}, g_23 = {:e}\n", coords->g_12(2, 2), coords->g_23(2, 2)); + + std::shared_ptr gen = + FieldFactory::get()->parse("source", Options::getRoot()->getSection("ne")); + output << "Ne::source = " << gen->str() << endl; + + return 0; } void GBS::LoadMetric(BoutReal Lnorm, BoutReal Bnorm) { - // Load metric coefficients from the mesh - Field2D Rxy, Bpxy, Btxy, hthe, sinty; - GRID_LOAD5(Rxy, Bpxy, Btxy, hthe, sinty); // Load metrics - - // Checking for dpsi and qinty used in BOUT grids - Field2D dx; - if (!mesh->get(dx, "dpsi")) { - output << "\tUsing dpsi as the x grid spacing\n"; - coords->setDx(dx); // Only use dpsi if found - } else { - // dx will have been read already from the grid - output << "\tUsing dx as the x grid spacing\n"; - } - - Rxy /= Lnorm; - hthe /= Lnorm; - sinty *= SQ(Lnorm) * Bnorm; - coords->setDx(coords->dx() / (SQ(Lnorm) * Bnorm)); - - Bpxy /= Bnorm; - Btxy /= Bnorm; - coords->setBxy(coords->Bxy() / Bnorm); - - // Calculate metric components - bool ShiftXderivs; - Options::getRoot()->get("shiftXderivs", ShiftXderivs, false); // Read global flag - if (ShiftXderivs) { - sinty = 0.0; // I disappears from metric - } - - BoutReal sbp = 1.0; // Sign of Bp - if (min(Bpxy, true) < 0.0) { - sbp = -1.0; - } - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(sinty) * g11 + SQ(coords->Bxy()) / g11; - const auto g12 = 0.0; - const auto g13 = -sinty * g11; - const auto g23 = -sbp * Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + SQ(sinty * Rxy); - const auto g_22 = SQ(coords->Bxy() * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = sbp * Btxy * hthe * sinty * Rxy / Bpxy; - const auto g_13 = sinty * Rxy * Rxy; - const auto g_23 = sbp * Btxy * hthe * Rxy / Bpxy; - - coords->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coords->setJ(hthe / Bpxy); -} + + auto tokamak_options = bout::TokamakOptions(*mesh); + + bool ShiftXderivs; + BoutReal shearFactor = 1.0; + Options::getRoot()->get("shiftXderivs", ShiftXderivs, false); // Read global flag + if (ShiftXderivs) { + shearFactor = 0.0; // I disappears from metric + } + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lnorm, Bnorm, shearFactor); // just define a macro for V_E dot Grad #define vE_Grad(f, p) (bracket(p, f, bm_exb)) -int GBS::rhs(BoutReal t) { - - output.print("TIME = {:e}\r", t); // Bypass logging, only to stdout - - // Communicate evolving variables - mesh->communicate(evars); - - // Floor small values - Te = floor(Te, 1e-3); - Ne = floor(Ne, 1e-3); - - // Solve phi from Vorticity - if (mms) { - // Solve for potential, adding a source term - Field3D phiS = FieldFactory::get()->create3D("phi:source", Options::getRoot(), mesh, - CELL_CENTRE, t); - phi = phiSolver->solve(Vort + phiS); - } else { - phi = phiSolver->solve(Vort); - } - - if (estatic) { - // Electrostatic - Ve = VePsi; - mesh->communicate(Ve); - } else { - aparSolver->setCoefA(-Ne * 0.5 * mi_me * beta_e); - psi = aparSolver->solve(Ne * (Vi - VePsi)); - - Ve = VePsi - 0.5 * mi_me * beta_e * psi; - mesh->communicate(psi, Ve); - } - - // Communicate auxilliary variables - mesh->communicate(phi); - - // Y boundary condition on phi - phi.applyBoundary(); - - // Stress tensor - - Field3D tau_e = Omega_ci * tau_e0 * pow(Te, 1.5) / Ne; // Normalised collision time - - Gi = 0.0; - if (ionvis) { - Field3D tau_i = Omega_ci * tau_i0 * pow(Ti, 1.5) / Ne; - Gi = -(0.96 * Ti * Ne * tau_i) * (2. * Grad_par(Vi) + C(phi) / coords->Bxy()); - mesh->communicate(Gi); - Gi.applyBoundary("neumann"); - } else { - mesh->communicate(Gi); - } - - Field3D logNe = log(Ne); - mesh->communicate(logNe); - - Ge = 0.0; - if (elecvis) { - Ge = -(0.73 * Te * Ne * tau_e) - * (2. * Grad_par(Ve) - + (5. * C(Te) + 5. * Te * C(logNe) + C(phi)) / coords->Bxy()); - mesh->communicate(Ge); - Ge.applyBoundary("neumann"); - } else { - mesh->communicate(Ge); - } - - // Collisional damping (normalised) - nu = 1. / (1.96 * Ne * tau_e * mi_me); - - Field3D Pe = Ne * Te; - - if (evolve_Ne) { - // Density - ddt(Ne) = -vE_Grad(Ne, phi) // ExB term - + (2. / coords->Bxy()) * (C(Pe) - Ne * C(phi)) // Perpendicular compression - + D(Ne, Dn) + H(Ne, Hn); - - if (parallel) { - ddt(Ne) -= - Ne * Grad_par(Ve) + Vpar_Grad_par(Ve, Ne); // Parallel compression, advection - } + int GBS::rhs(BoutReal t) { - if (!mms) { - // Source term - ddt(Ne) += Sn * where(Sn, 1.0, Ne); - } - } - - if (evolve_Te) { - // Electron temperature - ddt(Te) = -vE_Grad(Te, phi) - + (4. / 3.) * (Te / coords->Bxy()) - * ((7. / 2.) * C(Te) + (Te / Ne) * C(Ne) - C(phi)) - + D(Te, Dte) + H(Te, Hte); - - if (parallel) { - ddt(Te) -= Vpar_Grad_par(Ve, Te); - ddt(Te) += (2. / 3.) * Te - * (0.71 * Grad_par(Vi) - 1.71 * Grad_par(Ve) - + 0.71 * (Vi - Ve) * Grad_par(logNe)); - } + output.print("TIME = {:e}\r", t); // Bypass logging, only to stdout - if (!mms) { - // Source term. Note: Power source, so divide by Ne - ddt(Te) += Sp * where(Sp, 1.0, Te * Ne) / Ne; + // Communicate evolving variables + mesh->communicate(evars); - // Source of particles shouldn't include energy, so Ne*Te=const - // hence ddt(Te) = -(Te/Ne)*ddt(Ne) - ddt(Te) -= (Te / Ne) * Sn * where(Sn, 1.0, 0.0); - } - } - - if (evolve_Vort) { - // Vorticity - ddt(Vort) = -vE_Grad(Vort, phi) // ExB term - + 2. * coords->Bxy() * C(Pe) / Ne + coords->Bxy() * C(Gi) / (3. * Ne) - + D(Vort, Dvort) + H(Vort, Hvort); - - if (parallel) { - Field3D delV = Vi - Ve; - mesh->communicate(delV); - ddt(Vort) -= Vpar_Grad_par(Vi, Vort); // Parallel advection - ddt(Vort) += SQ(coords->Bxy()) * (Grad_par(delV) + (Vi - Ve) * Grad_par(logNe)); - } - } + // Floor small values + Te = floor(Te, 1e-3); + Ne = floor(Ne, 1e-3); - if (evolve_Ve) { - // Electron velocity + // Solve phi from Vorticity + if (mms) { + // Solve for potential, adding a source term + Field3D phiS = FieldFactory::get()->create3D("phi:source", Options::getRoot(), mesh, + CELL_CENTRE, t); + phi = phiSolver->solve(Vort + phiS); + } else { + phi = phiSolver->solve(Vort); + } - ddt(VePsi) = - -vE_Grad(Ve, phi) - Vpar_Grad_par(Ve, Ve) - mi_me * (2. / 3.) * Grad_par(Ge) - - mi_me * nu * (Ve - Vi) + mi_me * Grad_par(phi) - - mi_me * (Te * Grad_par(logNe) + 1.71 * Grad_par(Te)) + D(Ve, Dve) + H(Ve, Hve); - } + if (estatic) { + // Electrostatic + Ve = VePsi; + mesh->communicate(Ve); + } else { + aparSolver->setCoefA(-Ne * 0.5 * mi_me * beta_e); + psi = aparSolver->solve(Ne * (Vi - VePsi)); - if (evolve_Vi) { - // Ion velocity + Ve = VePsi - 0.5 * mi_me * beta_e * psi; + mesh->communicate(psi, Ve); + } - ddt(Vi) = -vE_Grad(Vi, phi) - Vpar_Grad_par(Vi, Vi) - (2. / 3.) * Grad_par(Gi) - - (Grad_par(Te) + Te * Grad_par(logNe)) // Parallel pressure - + D(Vi, Dvi) + H(Vi, Hvi); - } + // Communicate auxilliary variables + mesh->communicate(phi); - return 0; -} + // Y boundary condition on phi + phi.applyBoundary(); -const Field3D GBS::C(const Field3D& f) { // Curvature operator - Field3D g; //Temporary in case we need to communicate - switch (curv_method) { - case 0: - g = f; - mesh->communicate(g); - return V_dot_Grad(bxcv, g); - case 1: - g = f; - mesh->communicate(g); - return bxcv * Grad(g); - } - return coords->Bxy() * bracket(logB, f, BRACKET_ARAKAWA); -} + // Stress tensor -const Field3D GBS::D(const Field3D& f, BoutReal d) { // Diffusion operator - if (d < 0.0) { - return 0.0; - } - return d * Delp2(f); -} + Field3D tau_e = Omega_ci * tau_e0 * pow(Te, 1.5) / Ne; // Normalised collision time -const Field3D GBS::H(const Field3D& f, BoutReal h) { // Numerical hyper-diffusion operator - if (h < 0.0) { - return 0.0; - } - return -h * (dx4 * D4DX4(f) + dz4 * D4DZ4(f)); // + dy4*D4DY4(f) -} + Gi = 0.0; + if (ionvis) { + Field3D tau_i = Omega_ci * tau_i0 * pow(Ti, 1.5) / Ne; + Gi = -(0.96 * Ti * Ne * tau_i) * (2. * Grad_par(Vi) + C(phi) / coords->Bxy()); + mesh->communicate(Gi); + Gi.applyBoundary("neumann"); + } else { + mesh->communicate(Gi); + } + + Field3D logNe = log(Ne); + mesh->communicate(logNe); + + Ge = 0.0; + if (elecvis) { + Ge = -(0.73 * Te * Ne * tau_e) + * (2. * Grad_par(Ve) + + (5. * C(Te) + 5. * Te * C(logNe) + C(phi)) / coords->Bxy()); + mesh->communicate(Ge); + Ge.applyBoundary("neumann"); + } else { + mesh->communicate(Ge); + } + + // Collisional damping (normalised) + nu = 1. / (1.96 * Ne * tau_e * mi_me); + + Field3D Pe = Ne * Te; + + if (evolve_Ne) { + // Density + ddt(Ne) = -vE_Grad(Ne, phi) // ExB term + + (2. / coords->Bxy()) * (C(Pe) - Ne * C(phi)) // Perpendicular compression + + D(Ne, Dn) + H(Ne, Hn); + + if (parallel) { + ddt(Ne) -= + Ne * Grad_par(Ve) + Vpar_Grad_par(Ve, Ne); // Parallel compression, advection + } + + if (!mms) { + // Source term + ddt(Ne) += Sn * where(Sn, 1.0, Ne); + } + } + + if (evolve_Te) { + // Electron temperature + ddt(Te) = -vE_Grad(Te, phi) + + (4. / 3.) * (Te / coords->Bxy()) + * ((7. / 2.) * C(Te) + (Te / Ne) * C(Ne) - C(phi)) + + D(Te, Dte) + H(Te, Hte); + + if (parallel) { + ddt(Te) -= Vpar_Grad_par(Ve, Te); + ddt(Te) += (2. / 3.) * Te + * (0.71 * Grad_par(Vi) - 1.71 * Grad_par(Ve) + + 0.71 * (Vi - Ve) * Grad_par(logNe)); + } + + if (!mms) { + // Source term. Note: Power source, so divide by Ne + ddt(Te) += Sp * where(Sp, 1.0, Te * Ne) / Ne; + + // Source of particles shouldn't include energy, so Ne*Te=const + // hence ddt(Te) = -(Te/Ne)*ddt(Ne) + ddt(Te) -= (Te / Ne) * Sn * where(Sn, 1.0, 0.0); + } + } + + if (evolve_Vort) { + // Vorticity + ddt(Vort) = -vE_Grad(Vort, phi) // ExB term + + 2. * coords->Bxy() * C(Pe) / Ne + coords->Bxy() * C(Gi) / (3. * Ne) + + D(Vort, Dvort) + H(Vort, Hvort); + + if (parallel) { + Field3D delV = Vi - Ve; + mesh->communicate(delV); + ddt(Vort) -= Vpar_Grad_par(Vi, Vort); // Parallel advection + ddt(Vort) += SQ(coords->Bxy()) * (Grad_par(delV) + (Vi - Ve) * Grad_par(logNe)); + } + } + + if (evolve_Ve) { + // Electron velocity + + ddt(VePsi) = + -vE_Grad(Ve, phi) - Vpar_Grad_par(Ve, Ve) - mi_me * (2. / 3.) * Grad_par(Ge) + - mi_me * nu * (Ve - Vi) + mi_me * Grad_par(phi) + - mi_me * (Te * Grad_par(logNe) + 1.71 * Grad_par(Te)) + D(Ve, Dve) + H(Ve, Hve); + } + + if (evolve_Vi) { + // Ion velocity + + ddt(Vi) = -vE_Grad(Vi, phi) - Vpar_Grad_par(Vi, Vi) - (2. / 3.) * Grad_par(Gi) + - (Grad_par(Te) + Te * Grad_par(logNe)) // Parallel pressure + + D(Vi, Dvi) + H(Vi, Hvi); + } + + return 0; + } + + const Field3D GBS::C(const Field3D &f) { // Curvature operator + Field3D g; //Temporary in case we need to communicate + switch (curv_method) { + case 0: + g = f; + mesh->communicate(g); + return V_dot_Grad(bxcv, g); + case 1: + g = f; + mesh->communicate(g); + return bxcv * Grad(g); + } + return coords->Bxy() * bracket(logB, f, BRACKET_ARAKAWA); + } + + const Field3D GBS::D(const Field3D &f, BoutReal d) { // Diffusion operator + if (d < 0.0) { + return 0.0; + } + return d * Delp2(f); + } + + const Field3D GBS::H(const Field3D &f, BoutReal h) { // Numerical hyper-diffusion operator + if (h < 0.0) { + return 0.0; + } + return -h * (dx4 * D4DX4(f) + dz4 * D4DZ4(f)); // + dy4*D4DY4(f) + } // Standard main() function -BOUTMAIN(GBS); + BOUTMAIN(GBS); diff --git a/tests/MMS/elm-pb/elm_pb.cxx b/tests/MMS/elm-pb/elm_pb.cxx index 851b2d2a69..aef522b462 100644 --- a/tests/MMS/elm-pb/elm_pb.cxx +++ b/tests/MMS/elm-pb/elm_pb.cxx @@ -9,7 +9,6 @@ * *******************************************************************************/ -#include #include #include #include @@ -19,6 +18,7 @@ #include #include #include +#include #include #include @@ -114,10 +114,8 @@ class ELMpb : public PhysicsModel { BoutReal hyperresist; // Hyper-resistivity coefficient (in core only) BoutReal ehyperviscos; // electron Hyper-viscosity coefficient - // Metric coefficients - Field2D Rxy, Bpxy, Btxy, B0, hthe; - Field2D I; // Shear factor - + bout::TokamakOptions tokamak_options = bout::TokamakOptions(*mesh); + bool mms; // True if testing with Method of Manufactured Solutions const BoutReal MU0 = 4.0e-7 * PI; @@ -152,24 +150,6 @@ class ELMpb : public PhysicsModel { mesh->get(J0, "Jpar0"); // A / m^2 mesh->get(P0, "pressure"); // Pascals - // Load curvature term - b0xcv.covariant = false; // Read contravariant components - mesh->get(b0xcv, "bxcv"); // mixed units x: T y: m^-2 z: m^-2 - - // Load metrics - if (mesh->get(Rxy, "Rxy")) { // m - output_error.write("Error: Cannot read Rxy from grid\n"); - return 1; - } - if (mesh->get(Bpxy, "Bpxy")) { // T - output_error.write("Error: Cannot read Bpxy from grid\n"); - return 1; - } - mesh->get(Btxy, "Btxy"); // T - mesh->get(B0, "Bxy"); // T - mesh->get(hthe, "hthe"); // m - mesh->get(I, "sinty"); // m^-2 T^-1 - Coordinates* coords = mesh->getCoordinates(); ////////////////////////////////////////////////////////////// @@ -311,20 +291,23 @@ class ELMpb : public PhysicsModel { bool ShiftXderivs; globalOptions->get("shiftXderivs", ShiftXderivs, false); // Read global flag + BoutReal shearFactor = 1.0; if (ShiftXderivs) { if (mesh->IncIntShear) { // BOUT-06 style, using d/dx = d/dpsi + I * d/dz - coords->setIntShiftTorsion(I); + coords->setIntShiftTorsion(tokamak_options.I); } else { // Dimits style, using local coordinate system if (include_curvature) { - b0xcv.z += I * b0xcv.x; + b0xcv.z += tokamak_options.I * b0xcv.x; } - I = 0.0; // I disappears from metric + shearFactor = 0.0; // I disappears from metric } } + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lbar, Bbar, shearFactor); + ////////////////////////////////////////////////////////////// // NORMALISE QUANTITIES @@ -384,14 +367,6 @@ class ELMpb : public PhysicsModel { b0xcv.y *= Lbar * Lbar; b0xcv.z *= Lbar * Lbar; - Rxy /= Lbar; - Bpxy /= Bbar; - Btxy /= Bbar; - B0 /= Bbar; - hthe /= Lbar; - coords->setDx(coords->dx() / (Lbar * Lbar * Bbar)); - I *= Lbar * Lbar * Bbar; - BoutReal pnorm = max(P0, true); // Maximum over all processors vacuum_pressure *= pnorm; // Get pressure from fraction @@ -415,28 +390,6 @@ class ELMpb : public PhysicsModel { dump.add(eta, "eta", 0); - /**************** CALCULATE METRICS ******************/ - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(I) * g11 + SQ(B0) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + (SQ(I * Rxy)); - const auto g_22 = SQ(B0 * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; - - coords->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coords->setJ(hthe / Bpxy); - coords->setBxy(B0); - // Set B field vector B0vec.covariant = false; diff --git a/tests/MMS/tokamak/tokamak.cxx b/tests/MMS/tokamak/tokamak.cxx index 74c89bb95d..2c64218d5e 100644 --- a/tests/MMS/tokamak/tokamak.cxx +++ b/tests/MMS/tokamak/tokamak.cxx @@ -9,6 +9,7 @@ #include #include #include +#include class TokamakMMS : public PhysicsModel { public: @@ -43,61 +44,19 @@ class TokamakMMS : public PhysicsModel { return 0; } void LoadMetric(BoutReal Lnorm, BoutReal Bnorm) { - // Load metric coefficients from the mesh - Field2D Rxy, Bpxy, Btxy, hthe, sinty; - GRID_LOAD5(Rxy, Bpxy, Btxy, hthe, sinty); // Load metrics - Coordinates* coords = mesh->getCoordinates(); - - // Checking for dpsi used in BOUT grids - Field2D dx; - if (!mesh->get(dx, "dpsi")) { - output << "\tUsing dpsi as the x grid spacing\n"; - coords->setDx(dx); // Only use dpsi if found - } else { - // dx will have been read already from the grid - output << "\tUsing dx as the x grid spacing\n"; - } + auto tokamak_options = bout::TokamakOptions(*mesh); - Rxy /= Lnorm; - hthe /= Lnorm; - sinty *= SQ(Lnorm) * Bnorm; - coords->setDx(coords->dx() / (SQ(Lnorm) * Bnorm)); - - Bpxy /= Bnorm; - Btxy /= Bnorm; - coords->setBxy(coords->Bxy() / Bnorm); + Coordinates* coords = mesh->getCoordinates(); - // Calculate metric components bool ShiftXderivs; + BoutReal shearFactor = 1.0; Options::getRoot()->get("shiftXderivs", ShiftXderivs, false); // Read global flag if (ShiftXderivs) { - sinty = 0.0; // I disappears from metric + shearFactor = 0.0; // I disappears from metric } - BoutReal sbp = 1.0; // Sign of Bp - if (min(Bpxy, true) < 0.0) { - sbp = -1.0; - } - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(sinty) * g11 + SQ(coords->Bxy()) / g11; - const auto g12 = 0.0; - const auto g13 = -sinty * g11; - const auto g23 = -sbp * Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + SQ(sinty * Rxy); - const auto g_22 = SQ(coords->Bxy() * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = sbp * Btxy * hthe * sinty * Rxy / Bpxy; - const auto g_13 = sinty * Rxy * Rxy; - const auto g_23 = sbp * Btxy * hthe * Rxy / Bpxy; - - coords->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coords->setJ(hthe / Bpxy); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lnorm, Bnorm, shearFactor); } private: diff --git a/tests/integrated/test-drift-instability/2fluid.cxx b/tests/integrated/test-drift-instability/2fluid.cxx index f7b072cae5..291978fe6e 100644 --- a/tests/integrated/test-drift-instability/2fluid.cxx +++ b/tests/integrated/test-drift-instability/2fluid.cxx @@ -3,8 +3,8 @@ * Same as Maxim's version of BOUT - simplified 2-fluid for benchmarking *******************************************************************************/ -#include #include +#include #include #include @@ -41,9 +41,6 @@ class TwoFluid : public PhysicsModel { Field3D pei, pe; Field2D pei0, pe0; - // Metric coefficients - Field2D Rxy, Bpxy, Btxy, hthe; - // parameters BoutReal Te_x, Ti_x, Ni_x, Vi_x, bmag, rho_s, fmei, AA, ZZ; BoutReal lambda_ei, lambda_ii; @@ -62,13 +59,14 @@ class TwoFluid : public PhysicsModel { FieldGroup comms; // Group of variables for communications + bout::TokamakOptions tokamak_options = bout::TokamakOptions(*mesh); + Coordinates* coord; // Coordinate system CELL_LOC maybe_ylow; protected: int init(bool UNUSED(restarting)) override { - Field2D I; // Shear factor output.write("Solving 6-variable 2-fluid equations\n"); @@ -88,17 +86,7 @@ class TwoFluid : public PhysicsModel { b0xcv.covariant = false; // Read contravariant components mesh->get(b0xcv, "bxcv"); // b0xkappa terms - // Coordinate system coord = mesh->getCoordinates(); - - // Load metrics - GRID_LOAD(Rxy); - GRID_LOAD(Bpxy); - GRID_LOAD(Btxy); - GRID_LOAD(hthe); - coord->setDx(mesh->get("dpsi")); - mesh->get(I, "sinty"); - // Load normalisation values GRID_LOAD(Te_x); GRID_LOAD(Ti_x); @@ -146,7 +134,7 @@ class TwoFluid : public PhysicsModel { const bool ShiftXderivs = (*globalOptions)["ShiftXderivs"].withDefault(false); if (ShiftXderivs) { ShearFactor = 0.0; // I disappears from metric - b0xcv.z += I * b0xcv.x; + b0xcv.z += tokamak_options.I * b0xcv.x; } /************** CALCULATE PARAMETERS *****************/ @@ -200,41 +188,11 @@ class TwoFluid : public PhysicsModel { b0xcv.y *= rho_s * rho_s; b0xcv.z *= rho_s * rho_s; - // Normalise geometry - Rxy /= rho_s; - hthe /= rho_s; - I *= rho_s * rho_s * (bmag / 1e4) * ShearFactor; - coord->setDx(coord->dx() / (rho_s * rho_s * (bmag / 1e4))); - - // Normalise magnetic field - Bpxy /= (bmag / 1.e4); - Btxy /= (bmag / 1.e4); - coord->setBxy(coord->Bxy() / (bmag / 1.e4)); - // calculate pressures pei0 = (Ti0 + Te0) * Ni0; pe0 = Te0 * Ni0; - /**************** CALCULATE METRICS ******************/ - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(I) * g11 + SQ(coord->Bxy()) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + SQ(I * Rxy); - const auto g_22 = SQ(coord->Bxy() * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; - - coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coord->setJ(hthe / Bpxy); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, rho_s, bmag / 1e4, ShearFactor); /**************** SET EVOLVING VARIABLES *************/ diff --git a/tests/integrated/test-interchange-instability/2fluid.cxx b/tests/integrated/test-interchange-instability/2fluid.cxx index cddc81e91c..b12340610e 100644 --- a/tests/integrated/test-interchange-instability/2fluid.cxx +++ b/tests/integrated/test-interchange-instability/2fluid.cxx @@ -3,7 +3,7 @@ * Same as Maxim's version of BOUT - simplified 2-fluid for benchmarking *******************************************************************************/ -#include +#include #include #include @@ -26,9 +26,6 @@ class Interchange : public PhysicsModel { // Derived 3D variables Field3D phi; - // Metric coefficients - Field2D Rxy, Bpxy, Btxy, hthe; - // Parameters BoutReal Te_x, Ti_x, Ni_x, bmag, rho_s, AA, ZZ, wci; @@ -37,9 +34,10 @@ class Interchange : public PhysicsModel { Coordinates* coord; + bout::TokamakOptions tokamak_options = bout::TokamakOptions(*mesh); + protected: int init(bool UNUSED(restarting)) override { - Field2D I; // Shear factor output << "Solving 2-variable equations\n"; @@ -56,17 +54,7 @@ class Interchange : public PhysicsModel { b0xcv *= -1.0; // NOTE: THIS IS FOR 'OLD' GRID FILES ONLY - // Coordinate system coord = mesh->getCoordinates(); - - // Load metrics - GRID_LOAD(Rxy); - GRID_LOAD(Bpxy); - GRID_LOAD(Btxy); - GRID_LOAD(hthe); - coord->setDx(mesh->get("dpsi")); - mesh->get(I, "sinty"); - // Load normalisation values GRID_LOAD(Te_x); GRID_LOAD(Ti_x); @@ -95,7 +83,7 @@ class Interchange : public PhysicsModel { const bool ShiftXderivs = (*globalOptions)["ShiftXderivs"].withDefault(false); if (ShiftXderivs) { ShearFactor = 0.0; // I disappears from metric - b0xcv.z += I * b0xcv.x; + b0xcv.z += tokamak_options.I * b0xcv.x; } /************** CALCULATE PARAMETERS *****************/ @@ -112,6 +100,8 @@ class Interchange : public PhysicsModel { hthe0 / rho_s); } + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, rho_s, bmag / 1e4, ShearFactor); + /************** NORMALISE QUANTITIES *****************/ output.write("\tNormalising to rho_s = {:e}\n", rho_s); @@ -126,38 +116,6 @@ class Interchange : public PhysicsModel { b0xcv.y *= rho_s * rho_s; b0xcv.z *= rho_s * rho_s; - // Normalise geometry - Rxy /= rho_s; - hthe /= rho_s; - I *= rho_s * rho_s * (bmag / 1e4) * ShearFactor; - coord->setDx(coord->dx() / (rho_s * rho_s * (bmag / 1e4))); - - // Normalise magnetic field - Bpxy /= (bmag / 1.e4); - Btxy /= (bmag / 1.e4); - coord->setBxy(coord->Bxy() / (bmag / 1.e4)); - - /**************** CALCULATE METRICS ******************/ - - const auto g11 = SQ(Rxy * Bpxy); - const auto g22 = 1.0 / SQ(hthe); - const auto g33 = SQ(I) * g11 + SQ(coord->Bxy()) / g11; - const auto g12 = 0.0; - const auto g13 = -I * g11; - const auto g23 = -Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + SQ(I * Rxy); - const auto g_22 = SQ(coord->Bxy() * hthe / Bpxy); - const auto g_33 = Rxy * Rxy; - const auto g_12 = Btxy * hthe * I * Rxy / Bpxy; - const auto g_13 = I * Rxy * Rxy; - const auto g_23 = Btxy * hthe * Rxy / Bpxy; - - coord->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coord->setJ(hthe / Bpxy); - // Tell BOUT++ which variables to evolve SOLVE_FOR2(rho, Ni); diff --git a/tests/integrated/test-laplacexy/loadmetric.cxx b/tests/integrated/test-laplacexy/loadmetric.cxx index 3fccd4acce..a3e01acde8 100644 --- a/tests/integrated/test-laplacexy/loadmetric.cxx +++ b/tests/integrated/test-laplacexy/loadmetric.cxx @@ -1,20 +1,12 @@ -#include "bout/field2d.hxx" -#include "bout/globals.hxx" -#include "bout/mesh.hxx" -#include "bout/output.hxx" -#include "bout/utils.hxx" +#include #include "loadmetric.hxx" void LoadMetric(BoutReal Lnorm, BoutReal Bnorm) { - // Load metric coefficients from the mesh - Field2D Rxy, Bpxy, Btxy, hthe, sinty; auto mesh = bout::globals::mesh; auto coords = mesh->getCoordinates(); - GRID_LOAD5(Rxy, Bpxy, Btxy, hthe, sinty); // Load metrics - // Checking for dpsi and qinty used in BOUT grids Field2D dx; if (!mesh->get(dx, "dpsi")) { @@ -26,45 +18,16 @@ void LoadMetric(BoutReal Lnorm, BoutReal Bnorm) { } Field2D qinty; - Rxy /= Lnorm; - hthe /= Lnorm; - sinty *= SQ(Lnorm) * Bnorm; - coords->setDx(coords->dx() / (SQ(Lnorm) * Bnorm)); - - Bpxy /= Bnorm; - Btxy /= Bnorm; - coords->setBxy(coords->Bxy() / Bnorm); - // Calculate metric components std::string ptstr; Options::getRoot()->get("mesh:paralleltransform", ptstr, "identity"); // Convert to lower case for comparison ptstr = lowercase(ptstr); + BoutReal shearFactor = 1.0; if (ptstr == "shifted") { - sinty = 0.0; // I disappears from metric - } - - BoutReal sbp = 1.0; // Sign of Bp - if (min(Bpxy, true) < 0.0) { - sbp = -1.0; + shearFactor = 0.0; // I disappears from metric } - const auto g11 = pow(Rxy * Bpxy, 2); - const auto g22 = 1.0 / pow(hthe, 2); - const auto g33 = pow(sinty, 2) * g11 + pow(coords->Bxy(), 2) / g11; - const auto g12 = 0.0; - const auto g13 = -sinty * g11; - const auto g23 = -sbp * Btxy / (hthe * Bpxy * Rxy); - - const auto g_11 = 1.0 / g11 + pow(sinty * Rxy, 2); - const auto g_22 = pow(coords->Bxy() * hthe / Bpxy, 2); - const auto g_33 = Rxy * Rxy; - const auto g_12 = sbp * Btxy * hthe * sinty * Rxy / Bpxy; - const auto g_13 = sinty * Rxy * Rxy; - const auto g_23 = sbp * Btxy * hthe * Rxy / Bpxy; - - coords->setMetricTensor(ContravariantMetricTensor(g11, g22, g33, g12, g13, g23), - CovariantMetricTensor(g_11, g_22, g_33, g_12, g_13, g_23)); - - coords->setJ(hthe / Bpxy); + auto tokamak_options = bout::TokamakOptions(*mesh); + set_tokamak_coordinates_on_mesh(tokamak_options, *mesh, Lnorm, Bnorm, shearFactor); } diff --git a/tests/integrated/test_suite b/tests/integrated/test_suite index 307a8d84b3..77ad7882c4 100755 --- a/tests/integrated/test_suite +++ b/tests/integrated/test_suite @@ -188,7 +188,7 @@ class Test(threading.Thread): self.output += "\n(It is likely that a timeout occured)" else: # ❌ Failed - print("\u274C", end="") # No newline + print("\u274c", end="") # No newline print(" %7.3f s" % (time.time() - self.local.start_time), flush=True) def _cost(self):