diff --git a/gyrokinetic/apps/gk_field.c b/gyrokinetic/apps/gk_field.c index 8e81b86f67..108c1c82e8 100644 --- a/gyrokinetic/apps/gk_field.c +++ b/gyrokinetic/apps/gk_field.c @@ -306,9 +306,6 @@ gk_field_new(struct gkyl_gk *gk, struct gkyl_gyrokinetic_app *app) } } - // Initialize biased walls. - gk_field_biased_wall_new(app, f); - return f; } @@ -432,8 +429,5 @@ gk_field_release(const gkyl_gyrokinetic_app* app, struct gk_field *f) // Release energy diagnostics. gk_field_energy_release(app, f); - // Release biased walls. - gk_field_biased_wall_release(app, f); - gkyl_free(f); } diff --git a/gyrokinetic/apps/gk_field_biased_wall.c b/gyrokinetic/apps/gk_field_biased_wall.c deleted file mode 100644 index 3658b29a71..0000000000 --- a/gyrokinetic/apps/gk_field_biased_wall.c +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include -#include - -void -gk_field_biased_wall_new(struct gkyl_gyrokinetic_app *app, struct gk_field *f) -{ - f->phi_wall_lo = mkarr(app->use_gpu, app->basis.num_basis, app->local_ext.volume); - f->has_phi_wall_lo = false; - f->phi_wall_lo_evolve = false; - if (f->info.phi_wall_lo) - { - f->has_phi_wall_lo = true; - if (f->info.phi_wall_lo_evolve) { - f->phi_wall_lo_evolve = f->info.phi_wall_lo_evolve; - } - - f->phi_wall_lo_host = f->phi_wall_lo; - if (app->use_gpu) { - f->phi_wall_lo_host = mkarr(false, f->phi_wall_lo->ncomp, f->phi_wall_lo->size); - } - - f->phi_wall_lo_proj = gkyl_eval_on_nodes_new(&app->grid, &app->basis, - 1, f->info.phi_wall_lo, f->info.phi_wall_lo_ctx); - - // Compute phi_wall_lo at t = 0 - gkyl_eval_on_nodes_advance(f->phi_wall_lo_proj, 0.0, &app->local_ext, f->phi_wall_lo_host); - if (app->use_gpu) { // note: phi_wall_lo_host is same as phi_wall_lo when not on GPUs - gkyl_array_copy(f->phi_wall_lo, f->phi_wall_lo_host); - } - } - - // Set up biased upper wall (same size as electrostatic potential), by default is 0.0 - f->phi_wall_up = mkarr(app->use_gpu, app->basis.num_basis, app->local_ext.volume); - f->has_phi_wall_up = false; - f->phi_wall_up_evolve = false; - if (f->info.phi_wall_up) - { - f->has_phi_wall_up = true; - if (f->info.phi_wall_up_evolve) { - f->phi_wall_up_evolve = f->info.phi_wall_up_evolve; - } - - f->phi_wall_up_host = f->phi_wall_up; - if (app->use_gpu) { - f->phi_wall_up_host = mkarr(false, f->phi_wall_up->ncomp, f->phi_wall_up->size); - } - - f->phi_wall_up_proj = gkyl_eval_on_nodes_new(&app->grid, &app->basis, - 1, f->info.phi_wall_up, f->info.phi_wall_up_ctx); - - // Compute phi_wall_up at t = 0. - gkyl_eval_on_nodes_advance(f->phi_wall_up_proj, 0.0, &app->local_ext, f->phi_wall_up_host); - if (app->use_gpu) { // Note: phi_wall_up_host is same as phi_wall_up when not on GPUs. - gkyl_array_copy(f->phi_wall_up, f->phi_wall_up_host); - } - } -} - -void -gk_field_calc_phi_wall(gkyl_gyrokinetic_app *app, struct gk_field *field, double tm) -{ - if (field->has_phi_wall_lo && field->phi_wall_lo_evolve) { - gkyl_eval_on_nodes_advance(field->phi_wall_lo_proj, tm, &app->local_ext, field->phi_wall_lo_host); - if (app->use_gpu) { - gkyl_array_copy(field->phi_wall_lo, field->phi_wall_lo_host); - } - } - if (field->has_phi_wall_up && field->phi_wall_up_evolve) { - gkyl_eval_on_nodes_advance(field->phi_wall_up_proj, tm, &app->local_ext, field->phi_wall_up_host); - if (app->use_gpu) { - gkyl_array_copy(field->phi_wall_up, field->phi_wall_up_host); - } - } -} - -void -gk_field_biased_wall_release(const struct gkyl_gyrokinetic_app *app, struct gk_field *f) -{ - gkyl_array_release(f->phi_wall_lo); - if (f->has_phi_wall_lo) { - gkyl_eval_on_nodes_release(f->phi_wall_lo_proj); - if (app->use_gpu) { - gkyl_array_release(f->phi_wall_lo_host); - } - } - - gkyl_array_release(f->phi_wall_up); - if (f->has_phi_wall_up) { - gkyl_eval_on_nodes_release(f->phi_wall_up_proj); - if (app->use_gpu) { - gkyl_array_release(f->phi_wall_up_host); - } - } -} diff --git a/gyrokinetic/apps/gk_species.c b/gyrokinetic/apps/gk_species.c index 2b93be63cf..1c1b68d641 100644 --- a/gyrokinetic/apps/gk_species.c +++ b/gyrokinetic/apps/gk_species.c @@ -197,7 +197,8 @@ gk_species_rhs_implicit_static(gkyl_gyrokinetic_app *app, struct gk_species *spe } static void -gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species *species, struct gkyl_array *f) +gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species *species, + double tm, struct gkyl_array *f) { struct timespec wst = gkyl_wall_clock(); @@ -210,8 +211,9 @@ gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species * switch (species->lower_bc[d].type) { case GKYL_BC_GK_SPECIES_SHEATH: + gk_species_phi_wall_advance(app, &species->phi_wall_lo, tm); gkyl_bc_sheath_gyrokinetic_advance(species->bc_sheath_lo, app->field->phi_smooth, - app->field->phi_wall_lo, f, &app->local); + species->phi_wall_lo.phi, f, &app->local); break; case GKYL_BC_GK_SPECIES_TWISTSHIFT: gkyl_bc_twistshift_advance(species->bc_ts_lo, f, f); @@ -232,8 +234,9 @@ gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species * switch (species->upper_bc[d].type) { case GKYL_BC_GK_SPECIES_SHEATH: + gk_species_phi_wall_advance(app, &species->phi_wall_up, tm); gkyl_bc_sheath_gyrokinetic_advance(species->bc_sheath_up, app->field->phi_smooth, - app->field->phi_wall_up, f, &app->local); + species->phi_wall_up.phi, f, &app->local); break; case GKYL_BC_GK_SPECIES_TWISTSHIFT: gkyl_bc_twistshift_advance(species->bc_ts_up, f, f); @@ -266,7 +269,8 @@ gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species * } static void -gk_species_apply_bc_static(gkyl_gyrokinetic_app *app, const struct gk_species *species, struct gkyl_array *f) +gk_species_apply_bc_static(gkyl_gyrokinetic_app *app, const struct gk_species *species, + double tm, struct gkyl_array *f) { // do nothing } @@ -677,6 +681,7 @@ gk_species_release_dynamic(const gkyl_gyrokinetic_app* app, const struct gk_spec for (int d=0; dcdim; ++d) { if (s->lower_bc[d].type == GKYL_BC_GK_SPECIES_SHEATH) { gkyl_bc_sheath_gyrokinetic_release(s->bc_sheath_lo); + gk_species_phi_wall_release(app, &s->phi_wall_lo); } else if (s->lower_bc[d].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { gkyl_bc_twistshift_release(s->bc_ts_lo); @@ -690,6 +695,7 @@ gk_species_release_dynamic(const gkyl_gyrokinetic_app* app, const struct gk_spec if (s->upper_bc[d].type == GKYL_BC_GK_SPECIES_SHEATH) { gkyl_bc_sheath_gyrokinetic_release(s->bc_sheath_up); + gk_species_phi_wall_release(app, &s->phi_wall_up); } else if (s->upper_bc[d].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { gkyl_bc_twistshift_release(s->bc_ts_up); @@ -877,6 +883,7 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app struct gkyl_range *sol_ghost = gk_app_inp->geometry.has_LCFS? &gks->local_lower_ghost_par_sol : &gks->local_lower_ghost[d]; gks->bc_sheath_lo = gkyl_bc_sheath_gyrokinetic_new(d, GKYL_LOWER_EDGE, gks->basis_on_dev, sol_skin, sol_ghost, gks->vel_map, cdim, 2.0*(gks->info.charge/gks->info.mass), app->use_gpu); + gk_species_phi_wall_init(app, &gks->lower_bc[d], &gks->phi_wall_lo); } else if (gks->lower_bc[d].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { assert(cdim == 3); @@ -937,6 +944,7 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app struct gkyl_range *sol_ghost = gk_app_inp->geometry.has_LCFS? &gks->local_upper_ghost_par_sol : &gks->local_upper_ghost[d]; gks->bc_sheath_up = gkyl_bc_sheath_gyrokinetic_new(d, GKYL_UPPER_EDGE, gks->basis_on_dev, sol_skin, sol_ghost, gks->vel_map, cdim, 2.0*(gks->info.charge/gks->info.mass), app->use_gpu); + gk_species_phi_wall_init(app, &gks->upper_bc[d], &gks->phi_wall_up); } else if (gks->upper_bc[d].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { assert(cdim == 3); @@ -1497,7 +1505,7 @@ gk_species_init(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app *app, st gks->upper_bc[d].type = GKYL_BC_GK_SPECIES_PERIODIC; } } - + // Species properties metadata. struct gkyl_msgpack_map_elem io_meta_sprop[] = { { .key = "mass", .elem_type = GKYL_MP_DOUBLE, .dval = gks->info.mass }, @@ -1935,9 +1943,10 @@ gk_species_copy_range(struct gk_species *species, struct gkyl_array *out, } void -gk_species_apply_bc(gkyl_gyrokinetic_app *app, const struct gk_species *species, struct gkyl_array *f) +gk_species_apply_bc(gkyl_gyrokinetic_app *app, const struct gk_species *species, + double tm, struct gkyl_array *f) { - species->bc_func(app, species, f); + species->bc_func(app, species, tm, f); } void diff --git a/gyrokinetic/apps/gk_species_damping.c b/gyrokinetic/apps/gk_species_damping.c index 16669672d2..7600632679 100644 --- a/gyrokinetic/apps/gk_species_damping.c +++ b/gyrokinetic/apps/gk_species_damping.c @@ -1,16 +1,14 @@ #include #include -#include #include -#include void -gk_species_damping_write_disabled(gkyl_gyrokinetic_app* app, struct gk_species *gks, double tm, int frame) +gk_species_damping_write_disabled(gkyl_gyrokinetic_app *app, struct gk_species *gks, double tm, int frame) { } void -gk_species_damping_write_enabled(gkyl_gyrokinetic_app* app, struct gk_species *gks, double tm, int frame) +gk_species_damping_write_enabled(gkyl_gyrokinetic_app *app, struct gk_species *gks, double tm, int frame) { struct timespec wst = gkyl_wall_clock(); // DG metadata for damping rate. @@ -21,6 +19,7 @@ gk_species_damping_write_enabled(gkyl_gyrokinetic_app* app, struct gk_species *g { .key = "time", .elem_type = GKYL_MP_DOUBLE, .dval = tm }, { .key = "frame", .elem_type = GKYL_MP_UNSIGNED_INT, .uval = frame }, }; + int mpe_drate_len = sizeof(mpe_drate)/sizeof(mpe_drate[0]); // Package metadata. int io_meta_len[] = {gks->io_meta_basic_len, mpe_drate_len, app->gk_geom->io_meta_basic_len}; @@ -30,7 +29,7 @@ gk_species_damping_write_enabled(gkyl_gyrokinetic_app* app, struct gk_species *g // Write out the damping rate. const char *fmt = "%s-%s_damping_rate_%d.gkyl"; int sz = gkyl_calc_strlen(fmt, app->name, gks->info.name, frame); - char fileNm[sz+1]; // ensures no buffer overflow + char fileNm[sz + 1]; // ensures no buffer overflow snprintf(fileNm, sizeof fileNm, fmt, app->name, gks->info.name, frame); // Copy data from device to host before writing it out. @@ -40,12 +39,12 @@ gk_species_damping_write_enabled(gkyl_gyrokinetic_app* app, struct gk_species *g gkyl_comm_array_write(gks->comm, &gks->grid, &gks->local, mt, gks->damping.rate_host, fileNm); app->stat.n_io += 1; - gkyl_msgpack_data_release(mt); + gkyl_msgpack_data_release(mt); app->stat.species_diag_io_tm += gkyl_time_diff_now_sec(wst); } void -gk_species_damping_write_init_only(gkyl_gyrokinetic_app* app, struct gk_species *gks, double tm, int frame) +gk_species_damping_write_init_only(gkyl_gyrokinetic_app *app, struct gk_species *gks, double tm, int frame) { gk_species_damping_write_enabled(app, gks, tm, frame); gks->damping.write_func = gk_species_damping_write_disabled; @@ -74,10 +73,10 @@ gk_species_damping_init(struct gkyl_gyrokinetic_app *app, struct gk_species *gks if (damp->type) { // Allocate rate array. - damp->rate = mkarr(app->use_gpu, num_quad==1? 1 : gks->basis.num_basis, gks->local_ext.volume); + damp->rate = mkarr(app->use_gpu, num_quad == 1? 1 : gks->basis.num_basis, gks->local_ext.volume); damp->rate_host = damp->rate; if (app->use_gpu) - damp->rate_host = mkarr(false, damp->rate->ncomp, damp->rate->size); + damp->rate_host = mkarr(false, damp->rate->ncomp, damp->rate->size); if (damp->type == GKYL_GK_DAMPING_USER_INPUT) { struct gk_proj_on_basis_c2p_func_ctx proj_on_basis_c2p_ctx; // c2p function context. @@ -102,98 +101,6 @@ gk_species_damping_init(struct gkyl_gyrokinetic_app *app, struct gk_species *gks if (num_quad == 1) gkyl_array_scale_range(damp->rate, 1.0/pow(sqrt(2.0),gks->grid.ndim), &gks->local); } - else if (damp->type == GKYL_GK_DAMPING_LOSS_CONE) { - damp->evolve = true; // Since the loss cone boundary is proportional to phi(t). - - // Maximum bmag and its location. - // NOTE: if the same max bmag occurs at multiple locations, - // bmag_max_coord may have different values on different MPI processes. - double bmag_max_coord_ho[GKYL_MAX_CDIM]; - double bmag_max_ho = gkyl_gk_geometry_reduce_arg_bmag(app->gk_geom, GKYL_MAX, bmag_max_coord_ho); - double bmag_max_local = bmag_max_ho; - double bmag_max_global; - gkyl_comm_allreduce_host(app->comm, GKYL_DOUBLE, GKYL_MAX, 1, &bmag_max_local, &bmag_max_global); - double bmag_max_coord_local[app->cdim], bmag_max_coord_global[app->cdim]; - if (fabs(bmag_max_ho - bmag_max_global) < 1e-16) { - for (int d=0; dcdim; d++) - bmag_max_coord_local[d] = bmag_max_coord_ho[d]; - } - else { - for (int d=0; dcdim; d++) - bmag_max_coord_local[d] = -DBL_MAX; - } - gkyl_comm_allreduce_host(app->comm, GKYL_DOUBLE, GKYL_MAX, app->cdim, bmag_max_coord_local, bmag_max_coord_global); - - if (app->use_gpu) { - damp->bmag_max = gkyl_cu_malloc(sizeof(double)); - damp->bmag_max_coord = gkyl_cu_malloc(app->cdim*sizeof(double)); - gkyl_cu_memcpy(damp->bmag_max, &bmag_max_global, sizeof(double), GKYL_CU_MEMCPY_H2D); - gkyl_cu_memcpy(damp->bmag_max_coord, bmag_max_coord_ho, app->cdim*sizeof(double), GKYL_CU_MEMCPY_H2D); - } - else { - damp->bmag_max = gkyl_malloc(sizeof(double)); - damp->bmag_max_coord = gkyl_malloc(app->cdim*sizeof(double)); - memcpy(damp->bmag_max, &bmag_max_global, sizeof(double)); - memcpy(damp->bmag_max_coord, bmag_max_coord_ho, app->cdim*sizeof(double)); - } - - // Electrostatic potential at bmag_max_coord. - if (app->use_gpu) { - damp->phi_m = gkyl_cu_malloc(sizeof(double)); - damp->phi_m_global = gkyl_cu_malloc(sizeof(double)); - } - else { - damp->phi_m = gkyl_malloc(sizeof(double)); - damp->phi_m_global = gkyl_malloc(sizeof(double)); - } - - // Operator that projects the loss cone mask. - struct gkyl_loss_cone_mask_gyrokinetic_inp inp_proj = { - .phase_grid = &gks->grid, - .conf_basis = &app->basis, - .phase_basis = &gks->basis, - .conf_range = &app->local, - .conf_range_ext = &app->local_ext, - .vel_range = &gks->local_vel, - .vel_map = gks->vel_map, - .bmag = app->gk_geom->geo_int.bmag, - .bmag_max = damp->bmag_max, - .bmag_max_loc = damp->bmag_max_coord, - .mass = gks->info.mass, - .charge = gks->info.charge, - .num_quad = num_quad, - .use_gpu = app->use_gpu, - }; - damp->lcm_proj_op = gkyl_loss_cone_mask_gyrokinetic_inew( &inp_proj ); - - // Project the conf-space rate profile provided. - struct gkyl_array *scale_prof_high_order = mkarr(app->use_gpu, gks->basis.num_basis, gks->local_ext.volume); - struct gkyl_array *scale_prof_high_order_ho = app->use_gpu? mkarr(false, scale_prof_high_order->ncomp, scale_prof_high_order->size) - : gkyl_array_acquire(scale_prof_high_order); - - gkyl_proj_on_basis *projup = gkyl_proj_on_basis_new(&gks->grid, &gks->basis, num_quad, 1, - gks->info.damping.rate_profile, gks->info.damping.rate_profile_ctx); - gkyl_proj_on_basis_advance(projup, 0.0, &gks->local, scale_prof_high_order_ho); - gkyl_proj_on_basis_release(projup); - gkyl_array_copy(scale_prof_high_order, scale_prof_high_order_ho); - - damp->scale_prof = mkarr(app->use_gpu, num_quad == 1? 1 : gks->basis.num_basis, gks->local_ext.volume); - gkyl_array_set_offset(damp->scale_prof, pow(sqrt(2.0),gks->grid.ndim), scale_prof_high_order, 0); - - gkyl_array_release(scale_prof_high_order_ho); - gkyl_array_release(scale_prof_high_order); - - // Compute the initial damping rate (assuming phi=0 because phi hasn't been computed). - // Find the potential at the mirror throat. - gkyl_dg_basis_ops_eval_array_at_coord_comp(app->field->phi_smooth, damp->bmag_max_coord, - app->basis_on_dev, &app->grid, &app->local, damp->phi_m); - gkyl_comm_allreduce(app->comm, GKYL_DOUBLE, GKYL_MAX, 1, damp->phi_m, damp->phi_m_global); - // Project the loss cone mask. - gkyl_loss_cone_mask_gyrokinetic_advance(damp->lcm_proj_op, &gks->local, &app->local, - app->field->phi_smooth, damp->phi_m_global, damp->rate); - // Multiply by the user's scaling profile. - gkyl_array_scale_by_cell(damp->rate, damp->scale_prof); - } // Set function pointers chosen at runtime. if (damp->evolve) { @@ -206,7 +113,7 @@ gk_species_damping_init(struct gkyl_gyrokinetic_app *app, struct gk_species *gks } void -gk_species_damping_advance(gkyl_gyrokinetic_app *app, const struct gk_species *gks, struct gk_damping *damp, +gk_species_damping_advance(gkyl_gyrokinetic_app *app, const struct gk_species *gks, struct gk_damping *damp, const struct gkyl_array *phi, const struct gkyl_array *fin, struct gkyl_array *f_buffer, struct gkyl_array *rhs, struct gkyl_array *cflrate) { @@ -217,23 +124,6 @@ gk_species_damping_advance(gkyl_gyrokinetic_app *app, const struct gk_species *g gkyl_array_scale_by_cell(f_buffer, damp->rate); gkyl_array_accumulate(rhs, -1.0, f_buffer); } - else if (damp->type == GKYL_GK_DAMPING_LOSS_CONE) { - // Find the potential at the mirror throat. - gkyl_dg_basis_ops_eval_array_at_coord_comp(phi, damp->bmag_max_coord, - app->basis_on_dev, &app->grid, &app->local, damp->phi_m); - gkyl_comm_allreduce(app->comm, GKYL_DOUBLE, GKYL_MAX, 1, damp->phi_m, damp->phi_m_global); - - // Project the loss cone mask. - gkyl_loss_cone_mask_gyrokinetic_advance(damp->lcm_proj_op, &gks->local, &app->local, - phi, damp->phi_m_global, damp->rate); - - // Assemble the damping term -scale_prof * mask * f. - gkyl_array_set(f_buffer, 1.0, fin); - gkyl_array_scale_by_cell(damp->rate, damp->scale_prof); - gkyl_array_scale_by_cell(f_buffer, damp->rate); - gkyl_array_accumulate(rhs, -1.0, f_buffer); - - } // Add the frequency to the CFL frequency. gkyl_array_accumulate(cflrate, 1.0, damp->rate); @@ -243,7 +133,7 @@ gk_species_damping_advance(gkyl_gyrokinetic_app *app, const struct gk_species *g } void -gk_species_damping_write(gkyl_gyrokinetic_app* app, struct gk_species *gks, double tm, int frame) +gk_species_damping_write(gkyl_gyrokinetic_app *app, struct gk_species *gks, double tm, int frame) { gks->damping.write_func(app, gks, tm, frame); } @@ -253,27 +143,12 @@ gk_species_damping_release(const struct gkyl_gyrokinetic_app *app, const struct { if (damp->type) { gkyl_array_release(damp->rate); - if (app->use_gpu) + if (app->use_gpu) { gkyl_array_release(damp->rate_host); + } if (damp->type == GKYL_GK_DAMPING_USER_INPUT) { // Nothing to release. } - else if (damp->type == GKYL_GK_DAMPING_LOSS_CONE) { - if (app->use_gpu) { - gkyl_cu_free(damp->bmag_max); - gkyl_cu_free(damp->bmag_max_coord); - gkyl_cu_free(damp->phi_m); - gkyl_cu_free(damp->phi_m_global); - } - else { - gkyl_free(damp->bmag_max); - gkyl_free(damp->bmag_max_coord); - gkyl_free(damp->phi_m); - gkyl_free(damp->phi_m_global); - } - gkyl_loss_cone_mask_gyrokinetic_release(damp->lcm_proj_op); - gkyl_array_release(damp->scale_prof); - } } } diff --git a/gyrokinetic/apps/gk_species_fdot_multiplier.c b/gyrokinetic/apps/gk_species_fdot_multiplier.c index c24e77bfd5..2d7316200f 100644 --- a/gyrokinetic/apps/gk_species_fdot_multiplier.c +++ b/gyrokinetic/apps/gk_species_fdot_multiplier.c @@ -1,8 +1,23 @@ #include +#include #include #include -#include -#include + +static enum gkyl_gk_trapped_passing_orbit_type +trapped_passing_orbit_from_species_bc(enum gkyl_gyrokinetic_bc_type bc) +{ + switch (bc) { + case GKYL_BC_GK_SPECIES_SHEATH: + return GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH; + case GKYL_BC_GK_SPECIES_REFLECT: + case GKYL_BC_GK_SPECIES_ZERO_FLUX: + case GKYL_BC_GK_SPECIES_PERIODIC: + case GKYL_BC_GK_SPECIES_TWISTSHIFT: + return GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL; + default: + return GKYL_GK_TRAP_PASS_ORBIT_PASSING; + } +} static void gk_species_fdot_multiplier_write_disabled(gkyl_gyrokinetic_app *app, struct gk_species *gks, @@ -47,7 +62,6 @@ gk_species_fdot_multiplier_write_enabled(gkyl_gyrokinetic_app *app, struct gk_sp app->stat.n_io += 1; gkyl_msgpack_data_release(mt); - app->stat.species_diag_io_tm += gkyl_time_diff_now_sec(wst); } @@ -126,19 +140,20 @@ gk_species_fdot_multiplier_advance_loss_cone_mult(gkyl_gyrokinetic_app *app, const struct gkyl_array *f, const struct gkyl_array *cflrate, struct gkyl_array *combined_multiplier) { - // Find the potential at the mirror throat. - gkyl_dg_basis_ops_eval_array_at_coord_comp(phi, fdmul->bmag_max_coord, - app->basis_on_dev, &app->grid, &app->local, fdmul->phi_m); - gkyl_comm_allreduce(app->comm, GKYL_DOUBLE, GKYL_MAX, 1, fdmul->phi_m, fdmul->phi_m_global); - - // Project the loss cone mask into buffer. - gkyl_loss_cone_mask_gyrokinetic_advance(fdmul->lcm_proj_op, &gks->local, &app->local, - phi, fdmul->phi_m_global, fdmul->buffer); - - // Apply constant scale factor. + gkyl_comm_array_allgather(app->comm, &app->local, &app->global, phi, fdmul->phi_global); + int zdim = app->cdim - 1; + if (gks->lower_bc[zdim].type == GKYL_BC_GK_SPECIES_SHEATH) { + gkyl_comm_array_allgather(app->comm, &app->local, &app->global, + gks->phi_wall_lo.phi, fdmul->phi_wall_lo_global); + } + if (gks->upper_bc[zdim].type == GKYL_BC_GK_SPECIES_SHEATH) { + gkyl_comm_array_allgather(app->comm, &app->local, &app->global, + gks->phi_wall_up.phi, fdmul->phi_wall_up_global); + } + gkyl_loss_cone_mask_gyrokinetic_advance(fdmul->lcm_proj_op, &gks->local, &app->global, + fdmul->bmag_global, fdmul->phi_global, fdmul->phi_wall_lo_global, + fdmul->phi_wall_up_global, fdmul->buffer); gkyl_array_scale(fdmul->buffer, fdmul->time_dilation_scale_const); - - // Multiply into combined. gkyl_array_scale_by_cell(combined_multiplier, fdmul->buffer); } @@ -291,7 +306,7 @@ gk_species_fdot_multiplier_init_comp(gkyl_gyrokinetic_app *app, struct gk_specie fdmul->proj_on_basis_c2p_ctx.pos_map = app->position_map; fdmul->time_dilation_scale_const = fdot_mult_inp->time_dilation_scale_const; - if (fdmul->time_dilation_scale_const <= 0.0) { + if (fdmul->time_dilation_scale_const <= 0.0 || fdmul->time_dilation_scale_const >= 1.0) { fdmul->time_dilation_scale_const = 1.0; } @@ -322,77 +337,35 @@ gk_species_fdot_multiplier_init_comp(gkyl_gyrokinetic_app *app, struct gk_specie fdmul->advance_func = gk_species_fdot_multiplier_advance_mult; } else if (fdmul->type == GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE) { - fdmul->buffer = mkarr(app->use_gpu, basis_mult.num_basis, gks->local_ext.volume); - - enum gkyl_quad_type qtype = GKYL_GAUSS_LOBATTO_QUAD; - int num_quad = gks->basis.poly_order + 1; - - double bmag_max_coord_ho[GKYL_MAX_CDIM]; - double bmag_max_ho = gkyl_gk_geometry_reduce_arg_bmag(app->gk_geom, GKYL_MAX, - bmag_max_coord_ho); - double bmag_max_local = bmag_max_ho; - double bmag_max_global; - gkyl_comm_allreduce_host(app->comm, GKYL_DOUBLE, GKYL_MAX, 1, &bmag_max_local, - &bmag_max_global); - double bmag_max_coord_local[app->cdim], bmag_max_coord_global[app->cdim]; - if (fabs(bmag_max_ho - bmag_max_global) < 1e-16) { - for (int d = 0; d < app->cdim; d++) { - bmag_max_coord_local[d] = bmag_max_coord_ho[d]; - } - } - else { - for (int d = 0; d < app->cdim; d++) { - bmag_max_coord_local[d] = -DBL_MAX; - } - } - gkyl_comm_allreduce_host(app->comm, GKYL_DOUBLE, GKYL_MAX, app->cdim, bmag_max_coord_local, - bmag_max_coord_global); - - if (app->use_gpu) { - fdmul->bmag_max = gkyl_cu_malloc(sizeof(double)); - fdmul->bmag_max_coord = gkyl_cu_malloc(app->cdim * sizeof(double)); - gkyl_cu_memcpy(fdmul->bmag_max, &bmag_max_global, sizeof(double), GKYL_CU_MEMCPY_H2D); - gkyl_cu_memcpy(fdmul->bmag_max_coord, bmag_max_coord_ho, app->cdim * sizeof(double), - GKYL_CU_MEMCPY_H2D); - } - else { - fdmul->bmag_max = gkyl_malloc(sizeof(double)); - fdmul->bmag_max_coord = gkyl_malloc(app->cdim * sizeof(double)); - memcpy(fdmul->bmag_max, &bmag_max_global, sizeof(double)); - memcpy(fdmul->bmag_max_coord, bmag_max_coord_ho, app->cdim * sizeof(double)); - } - - if (app->use_gpu) { - fdmul->phi_m = gkyl_cu_malloc(sizeof(double)); - fdmul->phi_m_global = gkyl_cu_malloc(sizeof(double)); - } - else { - fdmul->phi_m = gkyl_malloc(sizeof(double)); - fdmul->phi_m_global = gkyl_malloc(sizeof(double)); - } - + // Operator that projects the loss cone mask. + fdmul->phi_wall_lo_global = 0; + fdmul->phi_wall_up_global = 0; + int zdim = app->cdim - 1; struct gkyl_loss_cone_mask_gyrokinetic_inp inp_proj = { - .phase_grid = &gks->grid, .conf_basis = &app->basis, - .phase_basis = &gks->basis, - .conf_range = &app->local, - .conf_range_ext = &app->local_ext, - .vel_range = &gks->local_vel, .vel_map = gks->vel_map, - .bmag = app->gk_geom->geo_int.bmag, - .bmag_max = fdmul->bmag_max, - .bmag_max_loc = fdmul->bmag_max_coord, .mass = gks->info.mass, .charge = gks->info.charge, - .qtype = qtype, - .num_quad = num_quad, - .cellwise_trap_loss = cellwise_const, - .c2p_pos_func = proj_on_basis_c2p_position_func, - .c2p_pos_func_ctx = &fdmul->proj_on_basis_c2p_ctx, .use_gpu = app->use_gpu, + .lower_orbit = trapped_passing_orbit_from_species_bc(gks->lower_bc[zdim].type), + .upper_orbit = trapped_passing_orbit_from_species_bc(gks->upper_bc[zdim].type), }; fdmul->lcm_proj_op = gkyl_loss_cone_mask_gyrokinetic_inew(&inp_proj); + fdmul->buffer = mkarr(app->use_gpu, basis_mult.num_basis, gks->local_ext.volume); + fdmul->bmag_global = mkarr(app->use_gpu, app->gk_geom->geo_corn.bmag->ncomp, + app->global_ext.volume); + fdmul->phi_global = mkarr(app->use_gpu, app->basis.num_basis, app->global_ext.volume); + if (inp_proj.lower_orbit == GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH) + fdmul->phi_wall_lo_global = mkarr(app->use_gpu, app->basis.num_basis, + app->global_ext.volume); + if (inp_proj.upper_orbit == GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH) + fdmul->phi_wall_up_global = mkarr(app->use_gpu, app->basis.num_basis, + app->global_ext.volume); + + gkyl_comm_array_allgather(app->comm, &app->local, &app->global, app->gk_geom->geo_corn.bmag, + fdmul->bmag_global); + fdmul->advance_func = gk_species_fdot_multiplier_advance_loss_cone_mult; } else if ((fdmul->type == GKYL_GK_FDOT_MULTIPLIER_FIXED_DT) || @@ -569,18 +542,12 @@ gk_species_fdot_multiplier_release_comp(const struct gkyl_gyrokinetic_app *app, } else if (fdmul->type == GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE) { gkyl_array_release(fdmul->buffer); - if (app->use_gpu) { - gkyl_cu_free(fdmul->bmag_max); - gkyl_cu_free(fdmul->bmag_max_coord); - gkyl_cu_free(fdmul->phi_m); - gkyl_cu_free(fdmul->phi_m_global); - } - else { - gkyl_free(fdmul->bmag_max); - gkyl_free(fdmul->bmag_max_coord); - gkyl_free(fdmul->phi_m); - gkyl_free(fdmul->phi_m_global); - } + gkyl_array_release(fdmul->bmag_global); + gkyl_array_release(fdmul->phi_global); + if (fdmul->phi_wall_lo_global) + gkyl_array_release(fdmul->phi_wall_lo_global); + if (fdmul->phi_wall_up_global) + gkyl_array_release(fdmul->phi_wall_up_global); gkyl_loss_cone_mask_gyrokinetic_release(fdmul->lcm_proj_op); } else if (fdmul->type == GKYL_GK_FDOT_MULTIPLIER_CONSTANT) { diff --git a/gyrokinetic/apps/gk_species_phi_wall.c b/gyrokinetic/apps/gk_species_phi_wall.c new file mode 100644 index 0000000000..c5f211947b --- /dev/null +++ b/gyrokinetic/apps/gk_species_phi_wall.c @@ -0,0 +1,64 @@ +#include +#include + +static void +gk_species_phi_wall_advance_disabled(gkyl_gyrokinetic_app *app, + const struct gk_species_wall_potential *wall, double tm) +{ +} + +static void +gk_species_phi_wall_advance_enabled(gkyl_gyrokinetic_app *app, + const struct gk_species_wall_potential *wall, double tm) +{ + gkyl_eval_on_nodes_advance(wall->projector, tm, &app->local_ext, wall->phi_host); + if (app->use_gpu) + gkyl_array_copy(wall->phi, wall->phi_host); +} + +void +gk_species_phi_wall_init(gkyl_gyrokinetic_app *app, + const struct gkyl_gyrokinetic_bc *bc, struct gk_species_wall_potential *wall) +{ + *wall = (struct gk_species_wall_potential) { + .advance_func = gk_species_phi_wall_advance_disabled, + }; + if (bc->type != GKYL_BC_GK_SPECIES_SHEATH) + return; + + wall->phi = mkarr(app->use_gpu, app->basis.num_basis, app->local_ext.volume); + gkyl_array_clear(wall->phi, 0.0); + + wall->phi_host = wall->phi; + if (bc->aux_profile) { + if (app->use_gpu) + wall->phi_host = mkarr(false, wall->phi->ncomp, wall->phi->size); + + wall->projector = gkyl_eval_on_nodes_new(&app->grid, &app->basis, + 1, bc->aux_profile, bc->aux_ctx); + wall->advance_func = gk_species_phi_wall_advance_enabled; + gk_species_phi_wall_advance(app, wall, 0.0); + } +} + +void +gk_species_phi_wall_advance(gkyl_gyrokinetic_app *app, + const struct gk_species_wall_potential *wall, double tm) +{ + wall->advance_func(app, wall, tm); +} + +void +gk_species_phi_wall_release(const gkyl_gyrokinetic_app *app, + const struct gk_species_wall_potential *wall) +{ + if (!wall->phi) + return; + + gkyl_array_release(wall->phi); + if (wall->projector) { + gkyl_eval_on_nodes_release(wall->projector); + if (app->use_gpu) + gkyl_array_release(wall->phi_host); + } +} diff --git a/gyrokinetic/apps/gkyl_gk_field_priv.h b/gyrokinetic/apps/gkyl_gk_field_priv.h index da502d8cd9..eee5b8281b 100644 --- a/gyrokinetic/apps/gkyl_gk_field_priv.h +++ b/gyrokinetic/apps/gkyl_gk_field_priv.h @@ -10,7 +10,7 @@ * This header contains forward declarations for internal field-related * functions used by the gyrokinetic application. These functions handle * FEM projections, field solves, boundary conditions, energy diagnostics, - * FLR corrections, polarization potentials, and biased wall configurations. + * FLR corrections and polarization potentials. */ /** FEM Initialization Functions **/ @@ -147,35 +147,3 @@ gk_field_invert_flr(gkyl_gyrokinetic_app *app, struct gk_field *field, void gk_field_invert_flr_none(gkyl_gyrokinetic_app *app, struct gk_field *field, struct gkyl_array *phi); - -/** Biased Wall Functions **/ - -/** - * Initialize biased wall boundary condition objects. - * Sets up machinery for applying time-dependent wall potentials. - * - * @param app Gyrokinetic application object. - * @param f Field object to initialize biased wall for. - */ -void -gk_field_biased_wall_new(struct gkyl_gyrokinetic_app *app, struct gk_field *f); - -/** - * Release biased wall resources. - * - * @param app Gyrokinetic application object. - * @param f Field object whose biased wall resources are to be released. - */ -void -gk_field_biased_wall_release(const struct gkyl_gyrokinetic_app *app, struct gk_field *f); - -/** - * Calculate and apply the wall potential at the current simulation time. - * Updates phi at the wall boundaries based on the biased wall configuration. - * - * @param app Gyrokinetic application object. - * @param field Field object containing the potential. - * @param tm Current simulation time. - */ -void -gk_field_calc_phi_wall(gkyl_gyrokinetic_app *app, struct gk_field *field, double tm); diff --git a/gyrokinetic/apps/gkyl_gyrokinetic.h b/gyrokinetic/apps/gkyl_gyrokinetic.h index e04e1510ba..669ad4ed8c 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic.h @@ -213,7 +213,8 @@ struct gkyl_gyrokinetic_bc { enum gkyl_edge_loc edge; // Which edge this BC is for. enum gkyl_gyrokinetic_bc_type type; // BC type flag. double value[3]; // Meaning depends on type. - void (*aux_profile)(double t, const double *xn, double *fout, void *ctx); // Auxiliary function (e.g. wall potential). + // Auxiliary profile. For a species sheath BC, this is the material-wall potential. + void (*aux_profile)(double t, const double *xn, double *fout, void *ctx); void *aux_ctx; // Context for aux_profile. struct gkyl_gyrokinetic_projection projection; // Projection object input (e.g. for FIXED_FUNC). struct gkyl_gyrokinetic_emission_inp emission; @@ -360,16 +361,13 @@ struct gkyl_gyrokinetic_positivity { enum gkyl_gyrokinetic_damping_type { GKYL_GK_DAMPING_NONE = 0, GKYL_GK_DAMPING_USER_INPUT, - GKYL_GK_DAMPING_LOSS_CONE, }; struct gkyl_gyrokinetic_damping { // Add a damping term to the RHS of the gyrokinetic equation // df/dt = - rate(z) * f // with the function rate(z) being: - // - a function given by the user (type = GKYL_PROPORTIONAL_TERM_USER_INPUT). - // - I_loss(z) * scale_factor * scale_profile(z), where I_loss(z) is =1 in the loss - // cone and 0 in the confined region (type = GKYL_PROPORTIONAL_TERM_LOSS_CONE). + // - a function given by the user (type = GKYL_GK_DAMPING_USER_INPUT). enum gkyl_gyrokinetic_damping_type type; void (*rate_profile)(double t, const double *xn, double *fout, void *ctx); void *rate_profile_ctx; // Context for rate_profile function. @@ -380,7 +378,7 @@ struct gkyl_gyrokinetic_damping { enum gkyl_gyrokinetic_fdot_multiplier_type { GKYL_GK_FDOT_MULTIPLIER_NONE = 0, // No multiplier applied. GKYL_GK_FDOT_MULTIPLIER_USER_INPUT, // User-provided static profile M(z) via function pointer. - GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE, // M=1 in loss cone, M=0 in confined region. + GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE, // M=1 in confined region, M=0 in the loss cone. GKYL_GK_FDOT_MULTIPLIER_CONSTANT, // Dilates time by a fixed constant, time_dilation_scale_const. GKYL_GK_FDOT_MULTIPLIER_FIXED_DT, // dt floor from user-specified cfl_dt_min_value. GKYL_GK_FDOT_MULTIPLIER_FIXED_FACTOR_TIMES_OMEGA_MAX, // dt floor from user-specified factor times maximum characteristic frequency. Specify cfl_factor_times_omega_max. @@ -572,16 +570,6 @@ struct gkyl_gyrokinetic_field { void (*init_field_profile)(double t, const double *xn, double *out, void *ctx); void *init_field_profile_ctx; - void *phi_wall_lo_ctx; // context for biased wall potential on lower wall - // pointer to biased wall potential on lower wall function - void (*phi_wall_lo)(double t, const double *xn, double *phi_wall_lo_out, void *ctx); - bool phi_wall_lo_evolve; // set to true if biased wall potential on lower wall function is time dependent - - void *phi_wall_up_ctx; // context for biased wall potential on upper wall - // pointer to biased wall potential on upper wall function - void (*phi_wall_up)(double t, const double *xn, double *phi_wall_up_out, void *ctx); - bool phi_wall_up_evolve; // set to true if biased wall potential on upper wall function is time dependent - struct gkyl_poisson_bias_line_list *bias_line_list; // Biased lines constraining the solution. }; diff --git a/gyrokinetic/apps/gkyl_gyrokinetic_multib.h b/gyrokinetic/apps/gkyl_gyrokinetic_multib.h index e47288d6f1..3706894eab 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic_multib.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic_multib.h @@ -110,15 +110,6 @@ struct gkyl_gyrokinetic_multib_field_pb { bool time_rate_diagnostics; // Writes the time rate of change of field energy. - void *phi_wall_lo_ctx; // context for biased wall potential on lower wall - // pointer to biased wall potential on lower wall function - void (*phi_wall_lo)(double t, const double *xn, double *phi_wall_lo_out, void *ctx); - bool phi_wall_lo_evolve; // set to true if biased wall potential on lower wall function is time dependent - - void *phi_wall_up_ctx; // context for biased wall potential on upper wall - // pointer to biased wall potential on upper wall function - void (*phi_wall_up)(double t, const double *xn, double *phi_wall_up_out, void *ctx); - bool phi_wall_up_evolve; // set to true if biased wall potential on upper wall function is time dependent }; // Field input diff --git a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h index 5615160973..91868eac24 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h @@ -843,11 +843,6 @@ struct gk_damping { bool evolve; // Whether the source is time dependent. struct gkyl_array *rate; // Damping rate. struct gkyl_array *rate_host; // Host copy for use in IO and projecting. - struct gkyl_loss_cone_mask_gyrokinetic *lcm_proj_op; // Operator that projects the loss cone mask. - double *bmag_max; // Maximum magnetic field amplitude. - double *bmag_max_coord; // Location of bmag_max. - double *phi_m, *phi_m_global; // Electrostatic potential at bmag_max. - struct gkyl_array *scale_prof; // Conf-space scaling factor profile. // Functions chosen at runtime. void (*write_func)(gkyl_gyrokinetic_app* app, struct gk_species *gks, double tm, int frame); }; @@ -858,10 +853,13 @@ struct gk_fdot_multiplier_comp { struct gkyl_array *buffer; // Per-component working storage. struct gkyl_array *buffer_ho; // Host copy for use in IO and projecting. struct gk_proj_on_basis_c2p_func_ctx proj_on_basis_c2p_ctx; // c2p function context. + + // Loss cone mask objects struct gkyl_loss_cone_mask_gyrokinetic *lcm_proj_op; // Operator that projects the loss cone mask. - double *bmag_max; // Maximum magnetic field amplitude. - double *bmag_max_coord; // Location of bmag_max. - double *phi_m, *phi_m_global; // Electrostatic potential at bmag_max. + struct gkyl_array *bmag_global; // Global bmag field used by the loss-cone updater. + struct gkyl_array *phi_global; // Global phi field used by the loss-cone updater. + struct gkyl_array *phi_wall_lo_global; // Global lower-wall potential used by the loss-cone updater. + struct gkyl_array *phi_wall_up_global; // Global upper-wall potential used by the loss-cone updater. // Time dilation parameters (from input). double cfl_dt_min_value; // User-specified minimum dt value. @@ -1000,6 +998,15 @@ struct gk_positivity { struct gk_positivity *pos); }; +// Projected material-wall potential associated with a species sheath BC. +struct gk_species_wall_potential { + struct gkyl_array *phi; + struct gkyl_array *phi_host; + gkyl_eval_on_nodes *projector; + void (*advance_func)(gkyl_gyrokinetic_app *app, + const struct gk_species_wall_potential *wall, double tm); +}; + // Species data. struct gk_species { struct gkyl_gyrokinetic_species info; // Input data. @@ -1062,6 +1069,8 @@ struct gk_species { // Boundary conditions on lower/upper edges in each direction. struct gkyl_gyrokinetic_bc lower_bc[GKYL_MAX_CDIM], upper_bc[GKYL_MAX_CDIM]; + // Wall potentials supplied by the parallel sheath BCs. + struct gk_species_wall_potential phi_wall_lo, phi_wall_up; // gyrokinetic sheath boundary conditions struct gkyl_bc_sheath_gyrokinetic *bc_sheath_lo; struct gkyl_bc_sheath_gyrokinetic *bc_sheath_up; @@ -1127,7 +1136,7 @@ struct gk_species { double (*rhs_implicit_func)(gkyl_gyrokinetic_app *app, struct gk_species *species, const struct gkyl_array *fin, struct gkyl_array *rhs, struct gkyl_array **bflux_moms, double dt); void (*bc_func)(gkyl_gyrokinetic_app *app, const struct gk_species *species, - struct gkyl_array *f); + double tm, struct gkyl_array *f); void (*release_func)(const gkyl_gyrokinetic_app* app, const struct gk_species *s); void (*step_f_func)(struct gkyl_array* out, double dt, const struct gkyl_array* inp); void (*combine_func)(struct gkyl_array *out, double c1, @@ -1368,18 +1377,6 @@ struct gk_field { gkyl_dynvec integ_energy_dot; // d/dt of integrated energy components. bool is_first_energy_dot_write_call; // flag for d(energy)/dt dynvec written first time - bool has_phi_wall_lo; // flag to indicate there is biased wall potential on lower wall - bool phi_wall_lo_evolve; // flag to indicate biased wall potential on lower wall is time dependent - struct gkyl_array *phi_wall_lo; // biased wall potential on lower wall - struct gkyl_array *phi_wall_lo_host; // host copy for use in IO and projecting - gkyl_eval_on_nodes *phi_wall_lo_proj; // projector for biased wall potential on lower wall - - bool has_phi_wall_up; // flag to indicate there is biased wall potential on upper wall - bool phi_wall_up_evolve; // flag to indicate biased wall potential on upper wall is time dependent - struct gkyl_array *phi_wall_up; // biased wall potential on upper wall - struct gkyl_array *phi_wall_up_host; // host copy for use in IO and projecting - gkyl_eval_on_nodes *phi_wall_up_proj; // projector for biased wall potential on upper wall - // Pointer to function that computes the time rate of change of the energy. void (*calc_energy_dt_func)(gkyl_gyrokinetic_app *app, const struct gk_field *field, double dt, double *energy_reduced); @@ -3244,9 +3241,11 @@ void gk_species_apply_pos_shift(gkyl_gyrokinetic_app* app, struct gk_species *gk * * @param app gyrokinetic app object. * @param species Pointer to species. + * @param tm Time at which to apply the BCs. * @param f Field to apply BCs. */ -void gk_species_apply_bc(gkyl_gyrokinetic_app *app, const struct gk_species *species, struct gkyl_array *f); +void gk_species_apply_bc(gkyl_gyrokinetic_app *app, const struct gk_species *species, + double tm, struct gkyl_array *f); /** * Fill stat object in app with collision timers. @@ -3340,6 +3339,35 @@ gk_species_calc_int_mom_dt(gkyl_gyrokinetic_app* app, struct gk_species *gks, do */ void gk_species_release(const gkyl_gyrokinetic_app* app, const struct gk_species *s); +/** + * Initialize a wall potential owned by a species sheath BC. + * + * @param app Gyrokinetic app object. + * @param bc Species sheath BC input. + * @param wall Wall-potential object. + */ +void gk_species_phi_wall_init(gkyl_gyrokinetic_app *app, + const struct gkyl_gyrokinetic_bc *bc, struct gk_species_wall_potential *wall); + +/** + * Evaluate a species sheath wall-potential profile at the requested time. + * + * @param app Gyrokinetic app object. + * @param wall Wall-potential object. + * @param tm Time at which to evaluate the profiles. + */ +void gk_species_phi_wall_advance(gkyl_gyrokinetic_app *app, + const struct gk_species_wall_potential *wall, double tm); + +/** + * Release a species sheath wall-potential resource. + * + * @param app Gyrokinetic app object. + * @param wall Wall-potential object. + */ +void gk_species_phi_wall_release(const gkyl_gyrokinetic_app *app, + const struct gk_species_wall_potential *wall); + /** gk_neut_species_moment API */ /** @@ -4135,15 +4163,6 @@ void gk_neut_species_release(const gkyl_gyrokinetic_app* app, const struct gk_ne */ struct gk_field* gk_field_new(struct gkyl_gk *gk, struct gkyl_gyrokinetic_app *app); -/** - * Compute biased wall potentials. - * - * @param app gyrokinetic app object. - * @param field Pointer to field. - * @param tm Time to compute biased wall potentials at. - */ -void gk_field_calc_phi_wall(gkyl_gyrokinetic_app *app, struct gk_field *field, double tm); - /** * Accumulate charge density for Poisson solve. * diff --git a/gyrokinetic/apps/gyrokinetic.c b/gyrokinetic/apps/gyrokinetic.c index 526aeebc67..4edc456b48 100644 --- a/gyrokinetic/apps/gyrokinetic.c +++ b/gyrokinetic/apps/gyrokinetic.c @@ -690,12 +690,6 @@ gyrokinetic_calc_field_enabled(gkyl_gyrokinetic_app* app, double tcurr, // Compute electrostatic potential from gyrokinetic Poisson's equation. gk_field_accumulate_rho_c(app, app->field, fin, bflux); - // Compute biased wall potential if present and time-dependent. - // Note: biased wall potential use eval_on_nodes. - // so does copy to GPU every call if app->use_gpu = true. - if (app->field->phi_wall_lo_evolve || app->field->phi_wall_up_evolve) - gk_field_calc_phi_wall(app, app->field, tcurr); - // Solve the field equation. gk_field_rhs(app, app->field); app->stat.field_tm += gkyl_time_diff_now_sec(wtm); @@ -999,7 +993,7 @@ gyrokinetic_calc_field_and_apply_bc(gkyl_gyrokinetic_app* app, double tcurr, // Apply boundary conditions. struct timespec wst = gkyl_wall_clock(); for (int i=0; inum_species; ++i) { - gk_species_apply_bc(app, &app->species[i], distf[i]); + gk_species_apply_bc(app, &app->species[i], tcurr, distf[i]); } for (int i=0; inum_neut_species; ++i) { gk_neut_species_apply_bc(app, &app->neut_species[i], distf_neut[i]); @@ -1115,7 +1109,7 @@ gkyl_gyrokinetic_app_apply_ic(gkyl_gyrokinetic_app* app, double t0) // Apply boundary conditions. for (int i=0; inum_species; ++i) { - gk_species_apply_bc(app, &app->species[i], distf[i]); + gk_species_apply_bc(app, &app->species[i], t0, distf[i]); } for (int i=0; inum_neut_species; ++i) { if (!app->neut_species[i].info.is_static) { @@ -3506,7 +3500,7 @@ gkyl_gyrokinetic_app_read_from_frame(gkyl_gyrokinetic_app *app, int frame) // Apply boundary conditions. for (int i=0; inum_species; ++i) { - gk_species_apply_bc(app, &app->species[i], distf[i]); + gk_species_apply_bc(app, &app->species[i], rstat.stime, distf[i]); } for (int i=0; inum_neut_species; ++i) { gk_neut_species_apply_bc(app, &app->neut_species[i], distf_neut[i]); diff --git a/gyrokinetic/apps/gyrokinetic_multib.c b/gyrokinetic/apps/gyrokinetic_multib.c index de0352dee2..792032fb92 100644 --- a/gyrokinetic/apps/gyrokinetic_multib.c +++ b/gyrokinetic/apps/gyrokinetic_multib.c @@ -193,10 +193,8 @@ singleb_app_new_solver(const struct gkyl_gyrokinetic_multib *mbinp, int bid, species_inp.polarization_density = sp_pb->polarization_density; // By default, skip BCs altogether. - for (int i=0; i<2*GKYL_MAX_CDIM; i++) { + for (int i=0; i<2*GKYL_MAX_CDIM; i++) species_inp.bcs[i].type = GKYL_BC_GK_SKIP; - species_inp.bcs[i].type = GKYL_BC_GK_SKIP; - } int pardir = cdim-1; int num_below = gkyl_multib_conn_get_num_connected(mbapp->block_topo, @@ -285,10 +283,8 @@ singleb_app_new_solver(const struct gkyl_gyrokinetic_multib *mbinp, int bid, neut_species_inp.source = nsp_pb->source; // By default, skip BCs altogether. - for (int i=0; i<2*GKYL_MAX_CDIM; i++) { - neut_species_inp.bcs[i].type = GKYL_BC_GK_SKIP; + for (int i=0; i<2*GKYL_MAX_CDIM; i++) neut_species_inp.bcs[i].type = GKYL_BC_GK_SKIP; - } // Set species physical BCs. int bc_count_nsp[num_blocks]; @@ -339,7 +335,7 @@ singleb_app_new_solver(const struct gkyl_gyrokinetic_multib *mbinp, int bid, field_inp.poisson_bcs[i].value[k] = -1.0e3; } } - + for (int i=0; inum_physical_bcs; i++) { if (bid == fld->bcs[i].bidx) { struct gkyl_gyrokinetic_bc *bc_curr = gk_fetch_bc_with_dir_edge(field_inp.poisson_bcs, 2*cdim, fld->bcs[i].dir, fld->bcs[i].edge); @@ -361,7 +357,7 @@ singleb_app_new_solver(const struct gkyl_gyrokinetic_multib *mbinp, int bid, } } } - + if (!fld->duplicate_across_blocks) { for (int i=0; iblocks[i].block_id) { @@ -374,15 +370,6 @@ singleb_app_new_solver(const struct gkyl_gyrokinetic_multib *mbinp, int bid, field_inp.polarization_bmag = fld_pb->polarization_bmag ? fld_pb->polarization_bmag : mbapp->bmag_ref; field_inp.kperpSq = fld_pb->kperpSq; field_inp.time_rate_diagnostics = fld_pb->time_rate_diagnostics; - - field_inp.phi_wall_lo_ctx = fld_pb->phi_wall_lo_ctx; - field_inp.phi_wall_lo = fld_pb->phi_wall_lo; - field_inp.phi_wall_lo_evolve = fld_pb->phi_wall_lo_evolve; - - field_inp.phi_wall_up_ctx = fld_pb->phi_wall_up_ctx; - field_inp.phi_wall_up = fld_pb->phi_wall_up; - field_inp.phi_wall_up_evolve = fld_pb->phi_wall_up_evolve; - // Copy field input into app input. memcpy(&app_inp.field, &field_inp, sizeof(struct gkyl_gyrokinetic_field)); @@ -932,7 +919,7 @@ gyrokinetic_multib_apply_bc(struct gkyl_gyrokinetic_multib_app* app, double tcur int li_charged = b * app->num_species; int li_neut = b * app->num_neut_species; for (int i=0; inum_species; ++i) { - gk_species_apply_bc(sbapp, &sbapp->species[i], distf[li_charged+i]); + gk_species_apply_bc(sbapp, &sbapp->species[i], tcurr, distf[li_charged+i]); } for (int i=0; inum_neut_species; ++i) { gk_neut_species_apply_bc(sbapp, &sbapp->neut_species[i], distf_neut[li_neut+i]); diff --git a/gyrokinetic/creg/rt_gk_mirror_boltz_elc_damped_1x2v_p1.c b/gyrokinetic/creg/rt_gk_mirror_boltz_elc_damped_1x2v_p1.c index d6f94e1324..954b775f79 100644 --- a/gyrokinetic/creg/rt_gk_mirror_boltz_elc_damped_1x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_mirror_boltz_elc_damped_1x2v_p1.c @@ -704,10 +704,7 @@ int main(int argc, char **argv) }, .damping = { -// .type = GKYL_GK_DAMPING_USER_INPUT, -// .rate_profile = loss_cone_damping_rate_profile, -// .rate_profile_ctx = &ctx, - .type = GKYL_GK_DAMPING_LOSS_CONE, + .type = GKYL_GK_DAMPING_USER_INPUT, .rate_profile = loss_cone_damping_rate_scaling, .rate_profile_ctx = &ctx, }, diff --git a/gyrokinetic/creg/rt_gk_mirror_boltz_elc_poa_1x2v_p1.c b/gyrokinetic/creg/rt_gk_mirror_boltz_elc_poa_1x2v_p1.c index 73f3e90e57..bff97fa34c 100644 --- a/gyrokinetic/creg/rt_gk_mirror_boltz_elc_poa_1x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_mirror_boltz_elc_poa_1x2v_p1.c @@ -30,80 +30,44 @@ struct gk_poa_phase_params { }; // Define the context of the simulation. This is basically all the globals -struct gk_mirror_ctx -{ +struct gk_mirror_ctx { int cdim, vdim; // Dimensionality. - // Plasma parameters - double mi; // Ion mass. - double me; // Electron mass. - double qi; // Ion charge. - double qe; // Electron charge. - double Te0; // Electron temperature. - double Ti0; // Ion temperature. - double n0; // Density. - double B_p; // Plasma magnetic field (mirror center). - double beta; // Plasma beta in the center. - double tau; // Temperature ratio. - - // Parameters controlling initial conditions. - double alim; - double alphaIC0; - double alphaIC1; - double Ti_perp0; // Reference ion perp temperature. - double Ti_par0; // Reference ion par temperature. - double Ti_perp_m; // Ion perp temperature at the throat. - double Ti_par_m; // Ion par temperature at the throat. - double cs_m; // Ion sound speed at the throat. - - double nuFrac; // Fraction multiplying collision frequency. - double logLambdaIon; // Ion Coulomb logarithm. - double nuIon; // Ion-ion collision freq. - - double vti; // Ion thermal speed. - double vte; // Electron thermal speed. - double c_s; // Ion sound speed. - double omega_ci; // Ion gyrofrequency. - double rho_s; // Ion sound gyroradius. - + double mi; + double qi; + double me; + double qe; + double Te0; + double n0; + double B_p; + double beta; + double tau; + double Ti0; + double nuFrac; + // Ion-ion collision freq. + double logLambdaIon; + double nuIon; + double vti; double RatZeq0; // Radius of the field line at Z=0. - double Z_min; // Minimum axial coordinate Z. - double Z_max; // Maximum axial coordinate Z. - double z_min; // Minimum value of the position along the field line. - double z_max; // Maximum value of the position along the field line. - double psi_eval; // Psi (poloidal flux) of the field line. - double psi_in, z_in; // Auxiliary psi and z. - - // Magnetic equilibrium model. - double mcB; - double gamma; - double Z_m; // Axial coordinate at mirror throat. - double z_m; // Computational coordinate at mirror throat. - - // Source parameters - double NSrcIon; - double lineLengthSrcIon; - double sigSrcIon; - double NSrcFloorIon; - double TSrc0Ion; - double TSrcFloorIon; - - // Physical velocity space limits. - double vpar_min_ion, vpar_max_ion; + // Axial coordinate Z extents. Endure that Z=0 is not on + double z_min; + double z_max; + double psi_eval; + // Physics parameters at mirror throat + double vpar_max_ion; double mu_max_ion; - // Computational velocity space limits. - double vpar_lin_fac_inv, mu_lin_fac_inv; // Inverse factor of where linear mapping ends. - double vpar_pow, mu_pow; // Power of the velocity grid. - double vpar_min_ion_c, vpar_max_ion_c; - double mu_min_ion_c, mu_max_ion_c; - - // Grid DOF. + int Npsi; int Nz; int Nvpar; int Nmu; int cells[GKYL_MAX_DIM]; // Number of cells in all directions. int poly_order; + // Source parameters + double ion_source_amplitude; + double ion_source_sigma; + double ion_source_temp; + double t_end; // End time. int num_frames; // Number of output frames. int num_phases; // Number of phases. @@ -112,6 +76,15 @@ struct gk_mirror_ctx double int_diag_calc_freq; // Frequency of calculating integrated diagnostics (as a factor of num_frames). double dt_failure_tol; // Minimum allowable fraction of initial time-step. int num_failures_max; // Maximum allowable number of consecutive small time-steps. + + // Geometry parameters for Lorentzian mirror + double mcB; // Magnetic field parameter + double gamma; // Width parameter for Lorentzian profile + double Z_m; // Mirror throat location + double Z_min; // Minimum Z coordinate + double Z_max; // Maximum Z coordinate + double psi_in; // Working variable for psi integration + double z_in; // Working variable for z integration }; double @@ -121,9 +94,10 @@ psi_RZ(double RIn, double ZIn, void *ctx) double mcB = app->mcB; double gamma = app->gamma; double Z_m = app->Z_m; + double psi = 0.5 * pow(RIn, 2.) * mcB * - (1. / (M_PI * gamma * (1. + pow((ZIn - Z_m) / gamma, 2.))) + - 1. / (M_PI * gamma * (1. + pow((ZIn + Z_m) / gamma, 2.)))); + (1. / (M_PI * gamma * (1. + pow((ZIn - Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn + Z_m) / gamma, 2.)))); return psi; } @@ -131,9 +105,13 @@ double R_psiZ(double psiIn, double ZIn, void *ctx) { struct gk_mirror_ctx *app = ctx; - double Rout = sqrt(2.0 * psiIn / (app->mcB * - (1.0 / (M_PI * app->gamma * (1.0 + pow((ZIn - app->Z_m) / app->gamma, 2.))) + - 1.0 / (M_PI * app->gamma * (1.0 + pow((ZIn + app->Z_m) / app->gamma, 2.)))))); + double mcB = app->mcB; + double gamma = app->gamma; + double Z_m = app->Z_m; + + double Rout = sqrt(2. * psiIn / (mcB * + (1. / (M_PI * gamma * (1. + pow((ZIn - Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn + Z_m) / gamma, 2.)))))); return Rout; } @@ -141,17 +119,21 @@ void Bfield_psiZ(double psiIn, double ZIn, void *ctx, double *BRad, double *BZ, double *Bmag) { struct gk_mirror_ctx *app = ctx; - double Rcoord = R_psiZ(psiIn, ZIn, ctx); double mcB = app->mcB; double gamma = app->gamma; double Z_m = app->Z_m; - *BRad = -(1.0 / 2.0) * Rcoord * mcB * - (-2.0 * (ZIn - Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn - Z_m) / gamma, 2.), 2.))) - - 2.0 * (ZIn + Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn + Z_m) / gamma, 2.), 2.)))); - *BZ = mcB * - (1.0 / (M_PI * gamma * (1.0 + pow((ZIn - Z_m) / gamma, 2.))) + - 1.0 / (M_PI * gamma * (1.0 + pow((ZIn + Z_m) / gamma, 2.)))); - *Bmag = sqrt(pow(*BRad, 2) + pow(*BZ, 2)); + + double Rcoord = R_psiZ(psiIn, ZIn, ctx); + + BRad[0] = -(1. / 2.) * Rcoord * mcB * + (-2. * (ZIn - Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn - Z_m) / gamma, 2.), 2.))) - + 2. * (ZIn + Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn + Z_m) / gamma, 2.), 2.)))); + + BZ[0] = mcB * + (1. / (M_PI * gamma * (1. + pow((ZIn - Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn + Z_m) / gamma, 2.))) ); + + Bmag[0] = sqrt(pow(BRad[0], 2) + pow(BZ[0], 2)); } double @@ -168,15 +150,13 @@ double z_psiZ(double psiIn, double ZIn, void *ctx) { struct gk_mirror_ctx *app = ctx; - app->psi_in = psiIn; double eps = 0.0; + app->psi_in = psiIn; struct gkyl_qr_res integral; - if (eps <= ZIn) - { + if (eps <= ZIn) { integral = gkyl_dbl_exp(integrand_z_psiZ, ctx, eps, ZIn, 7, 1e-14); } - else - { + else { integral = gkyl_dbl_exp(integrand_z_psiZ, ctx, ZIn, eps, 7, 1e-14); integral.res = -integral.res; } @@ -200,14 +180,12 @@ Z_psiz(double psiIn, double zIn, void *ctx) app->psi_in = psiIn; app->z_in = zIn; struct gkyl_qr_res Zout; - if (zIn >= 0.0) - { + if (0.0 <= zIn) { double fl = root_Z_psiz(-eps, ctx); double fr = root_Z_psiz(app->Z_max + eps, ctx); Zout = gkyl_ridders(root_Z_psiz, ctx, -eps, app->Z_max + eps, fl, fr, 1000, 1e-14); } - else - { + else { double fl = root_Z_psiz(app->Z_min - eps, ctx); double fr = root_Z_psiz(eps, ctx); Zout = gkyl_ridders(root_Z_psiz, ctx, app->Z_min - eps, eps, fl, fr, 1000, 1e-14); @@ -215,235 +193,128 @@ Z_psiz(double psiIn, double zIn, void *ctx) return Zout.res; } +// Geometry evaluation functions for the gk app void -eval_density_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +mapc2p(double t, const double *xc, double *GKYL_RESTRICT xp, void *ctx) { - double z = xn[0]; + double psi = xc[0], theta = xc[1], z = xc[2]; - struct gk_mirror_ctx *app = ctx; - double NSrc = app->NSrcIon; - double zSrc = app->lineLengthSrcIon; - double sigSrc = app->sigSrcIon; - double NSrcFloor = app->NSrcFloorIon; + double Z = Z_psiz(psi, z, ctx); + double R = R_psiZ(psi, Z, ctx); - double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. - double Z = Z_psiz(psi, z, ctx); // Cylindrical axial coordinate. + // Cartesian coordinates on plane perpendicular to Z axis. + double x = R * cos(theta); + double y = R * sin(theta); - if (fabs(Z) <= app->Z_m) - { - fout[0] = fmax(NSrcFloor, (NSrc / sqrt(2.0 * M_PI * pow(sigSrc, 2))) * - exp(-pow(z - zSrc, 2) / (2.0 * pow(sigSrc, 2)))); - } - else - { - fout[0] = 1e-16; - } + xp[0] = x; xp[1] = y; xp[2] = Z; } void -eval_upar_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +bfield_func(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) { - fout[0] = 0.0; + struct gk_mirror_ctx *app = ctx; + double z = xc[2]; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double Z = Z_psiz(psi, z, ctx); + double BRad, BZ, Bmag; + Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); + + double phi = xc[1]; + // zc are computational coords. + // Set Cartesian components of magnetic field. + fout[0] = BRad * cos(phi); + fout[1] = BRad * sin(phi); + fout[2] = BZ; } +// Evaluate collision frequencies void -eval_temp_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +evalNuIon(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) { - double z = xn[0]; - struct gk_mirror_ctx *app = ctx; - double sigSrc = app->sigSrcIon; - double TSrc0 = app->TSrc0Ion; - double Tfloor = app->TSrcFloorIon; - - if (fabs(z) <= 2.0 * sigSrc) - { - fout[0] = TSrc0; - } - else - { - fout[0] = Tfloor; - } + fout[0] = app->nuIon; } -// Ion initial conditions void eval_density_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) { - double z = xn[0]; - struct gk_mirror_ctx *app = ctx; - double z_m = app->z_m; - double sigma = 0.9*z_m; - if (fabs(z) <= sigma) - { - fout[0] = 0.5*app->n0*(1. + tanh(10. * sigma * fabs(sigma - fabs(z)))); - } - else - { - fout[0] = 0.5*app->n0*exp(-5 * (fabs(sigma - fabs(z)))); - } + double z = xn[0]; + fout[0] = 1e17 * exp(-2 * pow(fabs(z), 2)); } void eval_upar_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) { - double z = xn[0]; - struct gk_mirror_ctx *app = ctx; - double cs_m = app->cs_m; - double z_m = app->z_m; - double z_max = app->z_max; - if (fabs(z) <= z_m) - { - fout[0] = 0.0; - } - else - { - fout[0] = (fabs(z) / z) * cs_m * tanh(3 * (z_max - z_m) * fabs(fabs(z) - z_m)); - } + fout[0] = 0.0; } void -eval_temp_par_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +eval_temp_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) { - double z = xn[0]; - struct gk_mirror_ctx *app = ctx; - double z_m = app->z_m; - double Ti_par0 = app->Ti_par0; - double Ti_par_m = app->Ti_par_m; - if (fabs(z) <= z_m) - { - fout[0] = Ti_par_m+(Ti_par0-Ti_par_m)*tanh(4 * fabs(z_m - fabs(z))); - } - else - { - fout[0] = Ti_par_m; - } + fout[0] = app->Ti0; } void -eval_temp_perp_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +eval_density_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) { - double z = xn[0]; - struct gk_mirror_ctx *app = ctx; - double z_m = app->z_m; - double Ti_perp0 = app->Ti_perp0; - double Ti_perp_m = app->Ti_perp_m; - if (fabs(z) <= z_m) - { - fout[0] = Ti_perp_m - Ti_perp0*tanh(3.*fabs(z_m-fabs(z))); + double z = xn[0]; + double src_amp = app->ion_source_amplitude; + double z_src = 0.0; + double src_sigma = app->ion_source_sigma; + double src_amp_floor = src_amp * 1e-2; + if (fabs(z) <= 0.98) { + fout[0] = src_amp * (1 - pow(fabs(z), 6) / 0.98); } - else - { - fout[0] = Ti_perp_m * GKYL_MAX2(1.e-3, exp(-5. * (fabs(z_m - fabs(z))))); + else { + fout[0] = 1e-16; } } void -evalNuIon(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) -{ - struct gk_mirror_ctx *app = ctx; - fout[0] = app->nuIon; -} - -// Geometry evaluation functions for the gk app -// mapc2p must assume a 3d input xc -void -mapc2p(double t, const double *xc, double *GKYL_RESTRICT xp, void *ctx) +eval_upar_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) { - double psi = xc[0]; - double theta = xc[1]; - double z = xc[2]; - - double Z = Z_psiz(psi, z, ctx); - double R = R_psiZ(psi, Z, ctx); - - // Cartesian coordinates on plane perpendicular to Z axis. - double x = R * cos(theta); - double y = R * sin(theta); - xp[0] = x; - xp[1] = y; - xp[2] = Z; -} - -// bmag_func must assume a 3d input xc -void -bmag_func(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) -{ - double z = xc[2]; - - struct gk_mirror_ctx *app = ctx; - double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. - double Z = Z_psiz(psi, z, ctx); - double BRad, BZ, Bmag; - Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); - fout[0] = Bmag; + fout[0] = 0.0; } -// bfield_func must assume a 3d input xc void -bfield_func(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) +eval_temp_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) { - double z = xc[2]; - struct gk_mirror_ctx *app = ctx; - double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. - double Z = Z_psiz(psi, z, ctx); - double BRad, BZ, Bmag; - Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); - - double phi = xc[1]; - // zc are computational coords. - // Set Cartesian components of magnetic field. - fout[0] = BRad*cos(phi); - fout[1] = BRad*sin(phi); - fout[2] = BZ; + double z = xn[0]; + double TSrc0 = app->ion_source_temp; + double Tfloor = TSrc0 * 1e-2; + if (fabs(z) <= 0.98) { + fout[0] = TSrc0; + } + else { + fout[0] = Tfloor; + } } -void mapc2p_vel_ion(double t, const double *vc, double* GKYL_RESTRICT vp, void *ctx) +void mapc2p_vel_ion(double t, const double *vc, double *GKYL_RESTRICT vp, void *ctx) { struct gk_mirror_ctx *app = ctx; double vpar_max_ion = app->vpar_max_ion; double mu_max_ion = app->mu_max_ion; double cvpar = vc[0], cmu = vc[1]; - // Linear map up to vpar_max/lin_frac_inv, then a power grid. - double vpar_lin_fac_inv = app->vpar_lin_fac_inv; - double vpar_pow = app->vpar_pow; - if (fabs(cvpar) <= 1.0/vpar_lin_fac_inv) - vp[0] = vpar_max_ion*cvpar; - else if (cvpar < -1.0/vpar_lin_fac_inv) - vp[0] = -vpar_max_ion*pow(vpar_lin_fac_inv,vpar_pow-1)*pow(fabs(cvpar),vpar_pow); - else - vp[0] = vpar_max_ion*pow(vpar_lin_fac_inv,vpar_pow-1)*pow(fabs(cvpar),vpar_pow); - -// // Quadratic mu. -// vp[1] = mu_max_ion*pow(cmu,2.0); - // Linear map up to mu_max/lin_frac_inv, then a power grid. - double mu_lin_fac_inv = app->mu_lin_fac_inv; - double mu_pow = app->mu_pow; -// if (cmu <= 1.0/mu_lin_fac_inv) -// vp[0] = mu_max_ion*cmu; -// else -// vp[0] = mu_max_ion*pow(mu_lin_fac_inv,mu_pow-1)*pow(cmu,mu_pow); - double w = 0.3; - double f = 0.012; - double a = mu_max_ion*(f-1.0)/(w*w-1.0); - double b = mu_max_ion*(w*w-f)/(w*w-1.0); - if (cmu <= w) - vp[1] = (f*mu_max_ion/w)*cmu; - else - vp[1] = a*pow(cmu,2)+b; - + double b = 1.4; + vp[0] = vpar_max_ion * tan(cvpar * b) / tan(b); + vp[1] = mu_max_ion * pow(cmu, 3); } struct gk_mirror_ctx create_ctx(void) { int cdim = 1, vdim = 2; // Dimensionality. + int poly_order = 1; // Universal constant parameters. double eps0 = GKYL_EPSILON0; @@ -463,82 +334,39 @@ create_ctx(void) double tau = pow(B_p, 2.) * beta / (2.0 * mu0 * n0 * Te0) - 1.; double Ti0 = tau * Te0; - // Parameters controlling initial conditions. - double alim = 0.125; - double alphaIC0 = 2; - double alphaIC1 = 10; - - double nuFrac = 1.0; // Ion-ion collision freq. + double nuFrac = 1.0; double logLambdaIon = 6.6 - 0.5 * log(n0 / 1e20) + 1.5 * log(Ti0 / eV); double nuIon = nuFrac * logLambdaIon * pow(eV, 4.) * n0 / - (12 * pow(M_PI, 3. / 2.) * pow(eps0, 2.) * sqrt(mi) * pow(Ti0, 3. / 2.)); + (12 * pow(M_PI, 3. / 2.) * pow(eps0, 2.) * sqrt(mi) * pow(Ti0, 3. / 2.)); // Thermal speeds. double vti = sqrt(Ti0 / mi); - double vte = sqrt(Te0 / me); - double c_s = sqrt(Te0 / mi); - - // Gyrofrequencies and gyroradii. - double omega_ci = eV * B_p / mi; - double rho_s = c_s / omega_ci; - - // Geometry parameters. - double RatZeq0 = 0.10; // Radius of the field line at Z=0. - // Axial coordinate Z extents. Endure that Z=0 is not on - // the boundary of a cell (due to AD errors). - double Z_min = -2.5; - double Z_max = 2.5; - - // Parameters controlling the magnetic equilibrium model. - double mcB = 6.51292; - double gamma = 0.124904; - double Z_m = 0.98; - - // Source parameters - double NSrcIon = 3.1715e23 / 8.0 / 40.0 / 2.0 * 1.25; - double lineLengthSrcIon = 0.0; - double sigSrcIon = Z_m / 4.0; - double NSrcFloorIon = 0.05 * NSrcIon; - double TSrc0Ion = Ti0 * 1.25; - double TSrcFloorIon = TSrc0Ion / 8.0; // Grid parameters double vpar_max_ion = 16 * vti; - double vpar_min_ion = -vpar_max_ion; double mu_max_ion = mi * pow(3. * vti, 2.) / (2. * B_p); + int Nz = 64; + int Nvpar = 32; // 96 uniform + int Nmu = 16; // 192 uniform - // Computational velocity space limits. - double vpar_lin_fac_inv = 4; - double vpar_pow = 3; - double vpar_min_ion_c = -1.0/pow(vpar_lin_fac_inv,(vpar_pow-1)/vpar_pow); - double vpar_max_ion_c = 1.0/pow(vpar_lin_fac_inv,(vpar_pow-1)/vpar_pow); - double mu_min_ion_c = 0.; - double mu_max_ion_c = 1.; - double mu_lin_fac_inv = 1.0/0.012; - double mu_pow = 2; -// double mu_min_ion_c = 0.0; -// double mu_max_ion_c = 1.0/pow(mu_lin_fac_inv,(mu_pow-1)/mu_pow); - - // Grid DOF: - int Nz = 64; // Number of cells in z direction. - int Nvpar = 32; // Number of cells in parallel velocity direction. - int Nmu = 16; // Number of cells in mu direction. - int poly_order = 1; - - // Initial conditions parameter.s - double Ti_perp0 = 10000 * eV; - double Ti_par0 = 7500 * eV; + // Source parameters + double ion_source_amplitude = 1.e20; + double ion_source_sigma = 0.5; + double ion_source_temp = 5000. * eV; - // Parameters at mirror throat - double Ti_perp_m = 15000 * eV; - double Ti_par_m = 1000 * eV; - double z_m = 0.982544; - double cs_m = sqrt((Te0+3.0*Ti_par_m)/mi); + // Geometry parameters. + double RatZeq0 = 0.10; // Radius of the field line at Z=0. + double Z_min = -2.5; + double Z_max = 2.5; + double mcB = 3.691260; + double gamma = 0.226381; + double Z_m = 0.98; - // Factor multiplying collisionless terms. - double alpha_oap = 0.01; + // POA parameters + double alpha_oap = 5e-6; // Factor multiplying collisionless terms. double alpha_fdp = 1.0; + // Duration of each phase. double tau_oap = 100e-9; double tau_fdp = 1e-9; @@ -553,112 +381,105 @@ create_ctx(void) // Whether to evolve the field. bool is_static_field_oap = true; bool is_static_field_fdp = false; - // Whether to enable positivity. + + // Whether positivity is enabled. bool is_positivity_enabled_oap = false; bool is_positivity_enabled_fdp = true; + // Type of df/dt multipler. enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_oap = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE; enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_fdp = GKYL_GK_FDOT_MULTIPLIER_NONE; // Calculate phase structure - double t_end = (tau_oap + tau_fdp)*num_cycles + tau_fdp_extra; - double tau_pair = tau_oap+tau_fdp; // Duration of an OAP+FDP pair. - int num_phases = 2*num_cycles + 1; + double t_end = (tau_oap + tau_fdp) * num_cycles + tau_fdp_extra; + double tau_pair = tau_oap + tau_fdp; // Duration of an OAP+FDP pair. + int num_phases = 2 * num_cycles + 1; int num_frames = num_cycles * (num_frames_oap + num_frames_fdp) + num_frames_fdp_extra; - struct gk_poa_phase_params *poa_phases = gkyl_malloc(num_phases * sizeof(struct gk_poa_phase_params)); - for (int i=0; i<(num_phases-1)/2; i++) { + struct gk_poa_phase_params *poa_phases = gkyl_calloc(num_phases, + sizeof(struct gk_poa_phase_params)); + for (int i = 0; i < (num_phases - 1) / 2; i++) { // OAPs. - poa_phases[2*i].phase = GK_POA_OAP; - poa_phases[2*i].num_frames = num_frames_oap; - poa_phases[2*i].duration = tau_oap; - poa_phases[2*i].alpha = alpha_oap; - poa_phases[2*i].is_static_field = is_static_field_oap; - poa_phases[2*i].fdot_mult_type = fdot_mult_type_oap; - poa_phases[2*i].is_positivity_enabled = is_positivity_enabled_oap; + poa_phases[2 * i].phase = GK_POA_OAP; + poa_phases[2 * i].num_frames = num_frames_oap; + poa_phases[2 * i].duration = tau_oap; + poa_phases[2 * i].alpha = alpha_oap; + poa_phases[2 * i].is_static_field = is_static_field_oap; + poa_phases[2 * i].fdot_mult_type = fdot_mult_type_oap; + poa_phases[2 * i].is_positivity_enabled = is_positivity_enabled_oap; // FDPs. - poa_phases[2*i+1].phase = GK_POA_FDP; - poa_phases[2*i+1].num_frames = num_frames_fdp; - poa_phases[2*i+1].duration = tau_fdp; - poa_phases[2*i+1].alpha = alpha_fdp; - poa_phases[2*i+1].is_static_field = is_static_field_fdp; - poa_phases[2*i+1].fdot_mult_type = fdot_mult_type_fdp; - poa_phases[2*i+1].is_positivity_enabled = is_positivity_enabled_fdp; + poa_phases[2 * i + 1].phase = GK_POA_FDP; + poa_phases[2 * i + 1].num_frames = num_frames_fdp; + poa_phases[2 * i + 1].duration = tau_fdp; + poa_phases[2 * i + 1].alpha = alpha_fdp; + poa_phases[2 * i + 1].is_static_field = is_static_field_fdp; + poa_phases[2 * i + 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[2 * i + 1].is_positivity_enabled = is_positivity_enabled_fdp; } - // Add an extra, longer FDP. - poa_phases[num_phases-1].phase = GK_POA_FDP; - poa_phases[num_phases-1].num_frames = num_frames_fdp_extra; - poa_phases[num_phases-1].duration = tau_fdp_extra; - poa_phases[num_phases-1].alpha = alpha_fdp; - poa_phases[num_phases-1].is_static_field = is_static_field_fdp; - poa_phases[num_phases-1].fdot_mult_type = fdot_mult_type_fdp; - poa_phases[num_phases-1].is_positivity_enabled = is_positivity_enabled_fdp; - - double write_phase_freq = 0.5; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). + // The final stage is an extra, longer FDP. + poa_phases[num_phases - 1].phase = GK_POA_FDP; + poa_phases[num_phases - 1].num_frames = num_frames_fdp_extra; + poa_phases[num_phases - 1].duration = tau_fdp_extra; + poa_phases[num_phases - 1].alpha = alpha_fdp; + poa_phases[num_phases - 1].is_static_field = is_static_field_fdp; + poa_phases[num_phases - 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[num_phases - 1].is_positivity_enabled = is_positivity_enabled_fdp; + + double write_phase_freq = 1; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). double int_diag_calc_freq = 5; // Frequency of calculating integrated diagnostics (as a factor of num_frames). double dt_failure_tol = 1.0e-4; // Minimum allowable fraction of initial time-step. int num_failures_max = 20; // Maximum allowable number of consecutive small time-steps. struct gk_mirror_ctx ctx = { - .cdim = cdim, .vdim = vdim, - .mi = mi, .qi = qi, - .me = me, .qe = qe, - .Te0 = Te0, .Ti0 = Ti0, .n0 = n0, - .B_p = B_p, .beta = beta, .tau = tau, - .alim = alim, - .alphaIC0 = alphaIC0, - .alphaIC1 = alphaIC1, - .nuFrac = nuFrac, .logLambdaIon = logLambdaIon, .nuIon = nuIon, - .vti = vti, .vte = vte, .c_s = c_s, - .omega_ci = omega_ci, .rho_s = rho_s, + .cdim = cdim, + .vdim = vdim, + .mi = mi, + .qi = qi, + .me = me, + .qe = qe, + .Te0 = Te0, + .n0 = n0, + .B_p = B_p, + .beta = beta, + .tau = tau, + .Ti0 = Ti0, + .nuFrac = nuFrac, + .logLambdaIon = logLambdaIon, + .nuIon = nuIon, + .vti = vti, .RatZeq0 = RatZeq0, - .Z_min = Z_min, .Z_max = Z_max, - // Parameters controlling the magnetic equilibrium model. - .mcB = mcB, .gamma = gamma, - .Z_m = Z_m, - .z_m = z_m, - // Initial condition parameters. - .Ti_perp0 = Ti_perp0, .Ti_par0 = Ti_par0, - .Ti_perp_m = Ti_perp_m, .Ti_par_m = Ti_par_m, .cs_m = cs_m, - // Source parameters - .NSrcIon = NSrcIon, .NSrcFloorIon = NSrcFloorIon, - .TSrc0Ion = TSrc0Ion, .TSrcFloorIon = TSrcFloorIon, - .lineLengthSrcIon = lineLengthSrcIon, .sigSrcIon = sigSrcIon, - // Physical velocity space limits. - .vpar_min_ion = vpar_min_ion, .vpar_max_ion = vpar_max_ion, .mu_max_ion = mu_max_ion, - // Computational velocity space limits. - .vpar_lin_fac_inv = vpar_lin_fac_inv, - .vpar_pow = vpar_pow, - .vpar_min_ion_c = vpar_min_ion_c, - .vpar_max_ion_c = vpar_max_ion_c, - .mu_lin_fac_inv = mu_lin_fac_inv, - .mu_pow = mu_pow, - .mu_min_ion_c = mu_min_ion_c, - .mu_max_ion_c = mu_max_ion_c, - // Grid DOF. .Nz = Nz, .Nvpar = Nvpar, .Nmu = Nmu, - .cells = {Nz, Nvpar, Nmu}, + .cells = { Nz, Nvpar, Nmu }, .poly_order = poly_order, - // Time integration and I/O parameters. .t_end = t_end, .num_frames = num_frames, .num_phases = num_phases, .poa_phases = poa_phases, - .write_phase_freq = write_phase_freq , - .int_diag_calc_freq = int_diag_calc_freq , - .dt_failure_tol = dt_failure_tol , - .num_failures_max = num_failures_max , + .write_phase_freq = write_phase_freq, + .int_diag_calc_freq = int_diag_calc_freq, + .dt_failure_tol = dt_failure_tol, + .num_failures_max = num_failures_max, + + .ion_source_amplitude = ion_source_amplitude, + .ion_source_sigma = ion_source_sigma, + .ion_source_temp = ion_source_temp, + + .mcB = mcB, + .gamma = gamma, + .Z_m = Z_m, + .Z_min = Z_min, + .Z_max = Z_max, }; // Populate a couple more values in the context. ctx.psi_eval = psi_RZ(ctx.RatZeq0, 0., &ctx); - ctx.z_min = z_psiZ(ctx.psi_eval, ctx.Z_min, &ctx); - ctx.z_max = z_psiZ(ctx.psi_eval, ctx.Z_max, &ctx); + ctx.z_min = z_psiZ(ctx.psi_eval, ctx.Z_min, &ctx); + ctx.z_max = z_psiZ(ctx.psi_eval, ctx.Z_max, &ctx); return ctx; } @@ -670,25 +491,25 @@ release_ctx(struct gk_mirror_ctx *ctx) } void -calc_integrated_diagnostics(struct gkyl_tm_trigger* iot, gkyl_gyrokinetic_app* app, +calc_integrated_diagnostics(struct gkyl_tm_trigger *iot, gkyl_gyrokinetic_app *app, double t_curr, bool force_calc, double dt) { if (gkyl_tm_trigger_check_and_bump(iot, t_curr) || force_calc) { gkyl_gyrokinetic_app_calc_field_energy(app, t_curr); gkyl_gyrokinetic_app_calc_integrated_mom(app, t_curr); - if ( !(dt < 0.0) ) + if (!(dt < 0.0) ) gkyl_gyrokinetic_app_save_dt(app, t_curr, dt); } } void -write_data(struct gkyl_tm_trigger* iot_conf, struct gkyl_tm_trigger* iot_phase, - gkyl_gyrokinetic_app* app, double t_curr, bool force_write) +write_data(struct gkyl_tm_trigger *iot_conf, struct gkyl_tm_trigger *iot_phase, + gkyl_gyrokinetic_app *app, double t_curr, bool force_write) { bool trig_now_conf = gkyl_tm_trigger_check_and_bump(iot_conf, t_curr); if (trig_now_conf || force_write) { - int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr-1; + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; gkyl_gyrokinetic_app_write_conf(app, t_curr, frame); gkyl_gyrokinetic_app_write_field_energy(app); @@ -698,7 +519,7 @@ write_data(struct gkyl_tm_trigger* iot_conf, struct gkyl_tm_trigger* iot_phase, bool trig_now_phase = gkyl_tm_trigger_check_and_bump(iot_phase, t_curr); if (trig_now_phase || force_write) { - int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr-1; + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; gkyl_gyrokinetic_app_write_phase(app, t_curr, frame); } @@ -720,7 +541,7 @@ void reset_io_triggers(struct gk_mirror_ctx *ctx, struct time_frame_state *tfs, double t_end = tfs->t_end; int frame_curr = tfs->frame_curr; int num_frames = tfs->num_frames; - int num_int_diag_calc = ctx->int_diag_calc_freq*num_frames; + int num_int_diag_calc = ctx->int_diag_calc_freq * num_frames; // Prevent division by zero when frame_curr equals num_frames int frames_remaining = num_frames - frame_curr; @@ -734,15 +555,16 @@ void reset_io_triggers(struct gk_mirror_ctx *ctx, struct time_frame_state *tfs, trig_write_phase->tcurr = t_curr; trig_write_phase->curr = frame_curr; - int diag_frames = GKYL_MAX2(frames_remaining, (num_int_diag_calc/num_frames) * frames_remaining); + int diag_frames = GKYL_MAX2(frames_remaining, + (num_int_diag_calc / num_frames) * frames_remaining); trig_calc_intdiag->dt = time_remaining / diag_frames; trig_calc_intdiag->tcurr = t_curr; trig_calc_intdiag->curr = frame_curr; } -void run_phase(gkyl_gyrokinetic_app* app, struct gk_mirror_ctx *ctx, double num_steps, +void run_phase(gkyl_gyrokinetic_app *app, struct gk_mirror_ctx *ctx, double num_steps, struct gkyl_tm_trigger *trig_write_conf, struct gkyl_tm_trigger *trig_write_phase, - struct gkyl_tm_trigger *trig_calc_intdiag, struct time_frame_state *tfs, + struct gkyl_tm_trigger *trig_calc_intdiag, struct time_frame_state *tfs, struct gk_poa_phase_params *pparams) { tfs->t_end = tfs->t_curr + pparams->duration; @@ -751,7 +573,7 @@ void run_phase(gkyl_gyrokinetic_app* app, struct gk_mirror_ctx *ctx, double num_ // Run an OAP or FDP. double t_curr = tfs->t_curr; double t_end = tfs->t_end; - + // Reset I/O triggers: reset_io_triggers(ctx, tfs, trig_write_conf, trig_write_phase, trig_calc_intdiag); @@ -794,6 +616,7 @@ void run_phase(gkyl_gyrokinetic_app* app, struct gk_mirror_ctx *ctx, double num_ int num_failures = 0, num_failures_max = ctx->num_failures_max; long step = 1; + while ((t_curr < t_end) && (step <= num_steps)) { if (step == 1 || step % 1 == 0) @@ -801,6 +624,7 @@ void run_phase(gkyl_gyrokinetic_app* app, struct gk_mirror_ctx *ctx, double num_ dt = fmin(dt, t_end - t_curr); // Don't step beyond t_end. struct gkyl_update_status status = gkyl_gyrokinetic_update(app, dt); + gkyl_gyrokinetic_app_cout(app, stdout, " dt = %g\n", status.dt_actual); if (step == 1 || step % 1 == 0) gkyl_gyrokinetic_app_cout(app, stdout, " dt = %g\n", status.dt_actual); @@ -813,8 +637,8 @@ void run_phase(gkyl_gyrokinetic_app* app, struct gk_mirror_ctx *ctx, double num_ t_curr += status.dt_actual; dt = status.dt_suggested; - calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, t_curr > t_end, status.dt_actual); - write_data(trig_write_conf, trig_write_phase, app, t_curr, t_curr > t_end); + calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, t_curr >= t_end, status.dt_actual); + write_data(trig_write_conf, trig_write_phase, app, t_curr, t_curr >= t_end); if (dt_init < 0.0) { dt_init = status.dt_actual; @@ -826,8 +650,10 @@ void run_phase(gkyl_gyrokinetic_app* app, struct gk_mirror_ctx *ctx, double num_ gkyl_gyrokinetic_app_cout(app, stdout, " is below %g*dt_init ...", dt_failure_tol); gkyl_gyrokinetic_app_cout(app, stdout, " num_failures = %d\n", num_failures); if (num_failures >= num_failures_max) { - gkyl_gyrokinetic_app_cout(app, stdout, "ERROR: Time-step was below %g*dt_init ", dt_failure_tol); - gkyl_gyrokinetic_app_cout(app, stdout, "%d consecutive times. Aborting simulation ....\n", num_failures_max); + gkyl_gyrokinetic_app_cout(app, stdout, "ERROR: Time-step was below %g*dt_init ", + dt_failure_tol); + gkyl_gyrokinetic_app_cout(app, stdout, "%d consecutive times. Aborting simulation ....\n", + num_failures_max); calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, true, status.dt_actual); write_data(trig_write_conf, trig_write_phase, app, t_curr, true); break; @@ -841,7 +667,7 @@ void run_phase(gkyl_gyrokinetic_app* app, struct gk_mirror_ctx *ctx, double num_ } tfs->t_curr = t_curr; - tfs->frame_curr = tfs->frame_curr+pparams->num_frames; + tfs->frame_curr = tfs->frame_curr + pparams->num_frames; } int main(int argc, char **argv) @@ -860,64 +686,45 @@ int main(int argc, char **argv) struct gk_mirror_ctx ctx = create_ctx(); // Context for init functions. int cells_x[ctx.cdim], cells_v[ctx.vdim]; - for (int d=0; d 0) - { - gkyl_gyrokinetic_app_cout(app, stdout, "Max rel dt diff for RK stage-2 failures %g\n", stat.stage_2_dt_diff[1]); - gkyl_gyrokinetic_app_cout(app, stdout, "Min rel dt diff for RK stage-2 failures %g\n", stat.stage_2_dt_diff[0]); + if (stat.nstage_2_fail > 0) { + gkyl_gyrokinetic_app_cout(app, stdout, "Max rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[1]); + gkyl_gyrokinetic_app_cout(app, stdout, "Min rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[0]); } gkyl_gyrokinetic_app_cout(app, stdout, "Number of RK stage-3 failures %ld\n", stat.nstage_3_fail); gkyl_gyrokinetic_app_cout(app, stdout, "Number of write calls %ld\n", stat.n_io); gkyl_gyrokinetic_app_print_timings(app, stdout); - freeresources: +freeresources: // simulation complete, free app gkyl_gyrokinetic_app_release(app); gkyl_gyrokinetic_comms_release(comm); release_ctx(&ctx); - + #ifdef GKYL_HAVE_MPI if (app_args.use_mpi) MPI_Finalize(); diff --git a/gyrokinetic/creg/rt_gk_mirror_boltz_elc_poa_2x2v_p1.c b/gyrokinetic/creg/rt_gk_mirror_boltz_elc_poa_2x2v_p1.c new file mode 100644 index 0000000000..30a089746e --- /dev/null +++ b/gyrokinetic/creg/rt_gk_mirror_boltz_elc_poa_2x2v_p1.c @@ -0,0 +1,924 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +// State of the pseudo orbit-averaged integrator. +enum gk_poa_state { + GK_POA_NONE = 0, // Haven't started. + GK_POA_OAP, // Orbit averaged phase. + GK_POA_FDP, // Full dynamics phase. + GK_POA_COMPLETED, // Finished simulation. +}; + +struct gk_poa_phase_params { + enum gk_poa_state phase; // Type of phase. + int num_frames; // Number of frames. + double duration; // Duration. + double alpha; // Factor multiplying collisionless terms. + bool is_static_field; // Whether to evolve the field. + bool is_positivity_enabled; // Whether positivity is enabled. + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type; // Type of df/dt multipler. +}; + +// Define the context of the simulation. This is basically all the globals +struct gk_mirror_ctx { + int cdim, vdim; // Dimensionality. + // Plasma parameters + double mi; + double qi; + double me; + double qe; + double Te0; + double n0; + double B_p; + double beta; + double tau; + double Ti0; + double nuFrac; + // Ion-ion collision freq. + double logLambdaIon; + double nuIon; + double vti; + double RatZeq0; // Radius of the field line at Z=0. + // Axial coordinate Z extents. Endure that Z=0 is not on + double z_min; + double z_max; + double psi_max; + double psi_min; + // Physics parameters at mirror throat + double vpar_max_ion; + double mu_max_ion; + int Npsi; + int Nz; + int Nvpar; + int Nmu; + int cells[GKYL_MAX_DIM]; // Number of cells in all directions. + int poly_order; + + // Source parameters + double ion_source_amplitude; + double ion_source_sigma; + double ion_source_temp; + + double t_end; // End time. + int num_frames; // Number of output frames. + int num_phases; // Number of phases. + struct gk_poa_phase_params *poa_phases; // Phases to run. + double write_phase_freq; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). + double int_diag_calc_freq; // Frequency of calculating integrated diagnostics (as a factor of num_frames). + double dt_failure_tol; // Minimum allowable fraction of initial time-step. + int num_failures_max; // Maximum allowable number of consecutive small time-steps. + + // Geometry parameters for Lorentzian mirror + double mcB; // Magnetic field parameter + double gamma; // Width parameter for Lorentzian profile + double Z_m; // Mirror throat location + double Z_min; // Minimum Z coordinate + double Z_max; // Maximum Z coordinate + double psi_in; // Working variable for psi integration + double z_in; // Working variable for z integration +}; + +double +psi_RZ(double RIn, double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double mcB = app->mcB; + double gamma = app->gamma; + double Z_m = app->Z_m; + + double psi = 0.5 * pow(RIn, 2.) * mcB * + (1. / (M_PI * gamma * (1. + pow((ZIn - Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn + Z_m) / gamma, 2.)))); + return psi; +} + +double +R_psiZ(double psiIn, double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double mcB = app->mcB; + double gamma = app->gamma; + double Z_m = app->Z_m; + + double Rout = sqrt(2. * psiIn / (mcB * + (1. / (M_PI * gamma * (1. + pow((ZIn - Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn + Z_m) / gamma, 2.)))))); + return Rout; +} + +void +Bfield_psiZ(double psiIn, double ZIn, void *ctx, double *BRad, double *BZ, double *Bmag) +{ + struct gk_mirror_ctx *app = ctx; + double mcB = app->mcB; + double gamma = app->gamma; + double Z_m = app->Z_m; + + double Rcoord = R_psiZ(psiIn, ZIn, ctx); + + BRad[0] = -(1. / 2.) * Rcoord * mcB * + (-2. * (ZIn - Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn - Z_m) / gamma, 2.), 2.))) - + 2. * (ZIn + Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn + Z_m) / gamma, 2.), 2.)))); + + BZ[0] = mcB * + (1. / (M_PI * gamma * (1. + pow((ZIn - Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn + Z_m) / gamma, 2.))) ); + + Bmag[0] = sqrt(pow(BRad[0], 2) + pow(BZ[0], 2)); +} + +double +integrand_z_psiZ(double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = app->psi_in; + double BRad, BZ, Bmag; + Bfield_psiZ(psi, ZIn, ctx, &BRad, &BZ, &Bmag); + return Bmag / BZ; +} + +double +z_psiZ(double psiIn, double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double eps = 0.0; + app->psi_in = psiIn; + struct gkyl_qr_res integral; + if (eps <= ZIn) { + integral = gkyl_dbl_exp(integrand_z_psiZ, ctx, eps, ZIn, 7, 1e-14); + } + else { + integral = gkyl_dbl_exp(integrand_z_psiZ, ctx, ZIn, eps, 7, 1e-14); + integral.res = -integral.res; + } + return integral.res; +} + +// Invert z(Z) via root-finding. +double +root_Z_psiz(double Z, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + return app->z_in - z_psiZ(app->psi_in, Z, ctx); +} + +double +Z_psiz(double psiIn, double zIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double maxL = app->Z_max - app->Z_min; + double eps = maxL / app->Nz; // Interestingly using a smaller eps yields larger errors in some geo quantities. + app->psi_in = psiIn; + app->z_in = zIn; + struct gkyl_qr_res Zout; + if (0.0 <= zIn) { + double fl = root_Z_psiz(-eps, ctx); + double fr = root_Z_psiz(app->Z_max + eps, ctx); + Zout = gkyl_ridders(root_Z_psiz, ctx, -eps, app->Z_max + eps, fl, fr, 1000, 1e-14); + } + else { + double fl = root_Z_psiz(app->Z_min - eps, ctx); + double fr = root_Z_psiz(eps, ctx); + Zout = gkyl_ridders(root_Z_psiz, ctx, app->Z_min - eps, eps, fl, fr, 1000, 1e-14); + } + return Zout.res; +} + +// Geometry evaluation functions for the gk app +void +mapc2p(double t, const double *xc, double *GKYL_RESTRICT xp, void *ctx) +{ + double psi = xc[0], theta = xc[1], z = xc[2]; + + double Z = Z_psiz(psi, z, ctx); + double R = R_psiZ(psi, Z, ctx); + + // Cartesian coordinates on plane perpendicular to Z axis. + double x = R * cos(theta); + double y = R * sin(theta); + + xp[0] = x; xp[1] = y; xp[2] = Z; +} + +void +bfield_func(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xc[2]; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double Z = Z_psiz(psi, z, ctx); + double BRad, BZ, Bmag; + Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); + + double phi = xc[1]; + // zc are computational coords. + // Set Cartesian components of magnetic field. + fout[0] = BRad * cos(phi); + fout[1] = BRad * sin(phi); + fout[2] = BZ; +} + +// Evaluate collision frequencies +void +evalNuIon(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->nuIon; +} + +void +eval_density_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xn[1]; + fout[0] = 1e17 * exp(-2 * pow(fabs(z), 2)); +} + +void +eval_upar_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = 0.0; +} + +void +eval_temp_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->Ti0; +} + +void +eval_density_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xn[1]; + double src_amp = app->ion_source_amplitude; + double z_src = 0.0; + double src_sigma = app->ion_source_sigma; + double src_amp_floor = src_amp * 1e-2; + if (fabs(z) <= 0.98) { + fout[0] = src_amp * (1 - pow(fabs(z), 6) / 0.98); + } + else { + fout[0] = 1e-16; + } +} + +void +eval_upar_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + fout[0] = 0.0; +} + +void +eval_temp_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xn[1]; + double TSrc0 = app->ion_source_temp; + double Tfloor = TSrc0 * 1e-2; + if (fabs(z) <= 0.98) { + fout[0] = TSrc0; + } + else { + fout[0] = Tfloor; + } +} + +void mapc2p_vel_ion(double t, const double *vc, double *GKYL_RESTRICT vp, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double vpar_max_ion = app->vpar_max_ion; + double mu_max_ion = app->mu_max_ion; + + double cvpar = vc[0], cmu = vc[1]; + double b = 1.4; + vp[0] = vpar_max_ion * tan(cvpar * b) / tan(b); + vp[1] = mu_max_ion * pow(cmu, 3); +} + +struct gk_mirror_ctx +create_ctx(void) +{ + int cdim = 2, vdim = 2; // Dimensionality. + int poly_order = 1; + + // Universal constant parameters. + double eps0 = GKYL_EPSILON0; + double mu0 = GKYL_MU0; + double eV = GKYL_ELEMENTARY_CHARGE; + double mp = GKYL_PROTON_MASS; + double me = GKYL_ELECTRON_MASS; + double qi = eV; // ion charge + double qe = -eV; // electron charge + + // Plasma parameters. + double mi = 2.014 * mp; + double Te0 = 940 * eV; + double n0 = 3e19; + double B_p = 0.53; + double beta = 0.4; + double tau = pow(B_p, 2.) * beta / (2.0 * mu0 * n0 * Te0) - 1.; + double Ti0 = tau * Te0; + + // Ion-ion collision freq. + double nuFrac = 1.0; + double logLambdaIon = 6.6 - 0.5 * log(n0 / 1e20) + 1.5 * log(Ti0 / eV); + double nuIon = nuFrac * logLambdaIon * pow(eV, 4.) * n0 / + (12 * pow(M_PI, 3. / 2.) * pow(eps0, 2.) * sqrt(mi) * pow(Ti0, 3. / 2.)); + + // Thermal speeds. + double vti = sqrt(Ti0 / mi); + + // Grid parameters + double vpar_max_ion = 16 * vti; + double mu_max_ion = mi * pow(3. * vti, 2.) / (2. * B_p); + int Nz = 64; + int Npsi = 4; + int Nvpar = 32; // 96 uniform + int Nmu = 16; // 192 uniform + + // Source parameters + double ion_source_amplitude = 1.e20; + double ion_source_sigma = 0.5; + double ion_source_temp = 5000. * eV; + + // Geometry parameters. + double RatZeq0 = 0.10; // Radius of the field line at Z=0. + double Z_min = -2.5; + double Z_max = 2.5; + double mcB = 3.691260; + double gamma = 0.226381; + double Z_m = 0.98; + + // POA parameters + double alpha_oap = 5e-6; // Factor multiplying collisionless terms. + double alpha_fdp = 1.0; + double tau_oap = 0.001; // Duration of each phase. + double tau_fdp = 7e-9; + double tau_fdp_extra = 2e-9; + int num_cycles = 2; // Number of OAP+FDP cycles to run. + + // Frame counts for each phase type (specified independently) + int num_frames_oap = 1; // Frames per OAP phase + int num_frames_fdp = 1; // Frames per FDP phase + int num_frames_fdp_extra = 2; // Frames for the extra FDP phase + + // Whether to evolve the field. + bool is_static_field_oap = true; + bool is_static_field_fdp = false; + + // Whether positivity is enabled. + bool is_positivity_enabled_oap = false; + bool is_positivity_enabled_fdp = false; + + // Type of df/dt multipler. + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_oap = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE; + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_fdp = GKYL_GK_FDOT_MULTIPLIER_NONE; + + // Calculate phase structure + double t_end = (tau_oap + tau_fdp) * num_cycles + tau_fdp_extra; + double tau_pair = tau_oap + tau_fdp; // Duration of an OAP+FDP pair. + int num_phases = 2 * num_cycles + 1; + int num_frames = num_cycles * (num_frames_oap + num_frames_fdp) + num_frames_fdp_extra; + + struct gk_poa_phase_params *poa_phases = gkyl_calloc(num_phases, + sizeof(struct gk_poa_phase_params)); + for (int i = 0; i < (num_phases - 1) / 2; i++) { + // OAPs. + poa_phases[2 * i].phase = GK_POA_OAP; + poa_phases[2 * i].num_frames = num_frames_oap; + poa_phases[2 * i].duration = tau_oap; + poa_phases[2 * i].alpha = alpha_oap; + poa_phases[2 * i].is_static_field = is_static_field_oap; + poa_phases[2 * i].fdot_mult_type = fdot_mult_type_oap; + poa_phases[2 * i].is_positivity_enabled = is_positivity_enabled_oap; + + // FDPs. + poa_phases[2 * i + 1].phase = GK_POA_FDP; + poa_phases[2 * i + 1].num_frames = num_frames_fdp; + poa_phases[2 * i + 1].duration = tau_fdp; + poa_phases[2 * i + 1].alpha = alpha_fdp; + poa_phases[2 * i + 1].is_static_field = is_static_field_fdp; + poa_phases[2 * i + 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[2 * i + 1].is_positivity_enabled = is_positivity_enabled_fdp; + } + // The final stage is an extra, longer FDP. + poa_phases[num_phases - 1].phase = GK_POA_FDP; + poa_phases[num_phases - 1].num_frames = num_frames_fdp_extra; + poa_phases[num_phases - 1].duration = tau_fdp_extra; + poa_phases[num_phases - 1].alpha = alpha_fdp; + poa_phases[num_phases - 1].is_static_field = is_static_field_fdp; + poa_phases[num_phases - 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[num_phases - 1].is_positivity_enabled = is_positivity_enabled_fdp; + + double write_phase_freq = 1; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). + double int_diag_calc_freq = 5; // Frequency of calculating integrated diagnostics (as a factor of num_frames). + double dt_failure_tol = 1.0e-4; // Minimum allowable fraction of initial time-step. + int num_failures_max = 20; // Maximum allowable number of consecutive small time-steps. + + struct gk_mirror_ctx ctx = { + .cdim = cdim, + .vdim = vdim, + .mi = mi, + .qi = qi, + .me = me, + .qe = qe, + .Te0 = Te0, + .n0 = n0, + .B_p = B_p, + .beta = beta, + .tau = tau, + .Ti0 = Ti0, + .nuFrac = nuFrac, + .logLambdaIon = logLambdaIon, + .nuIon = nuIon, + .vti = vti, + .RatZeq0 = RatZeq0, + .vpar_max_ion = vpar_max_ion, + .mu_max_ion = mu_max_ion, + .Npsi = Npsi, + .Nz = Nz, + .Nvpar = Nvpar, + .Nmu = Nmu, + .cells = { Npsi, Nz, Nvpar, Nmu }, + .poly_order = poly_order, + .t_end = t_end, + .num_frames = num_frames, + .num_phases = num_phases, + .poa_phases = poa_phases, + .write_phase_freq = write_phase_freq, + .int_diag_calc_freq = int_diag_calc_freq, + .dt_failure_tol = dt_failure_tol, + .num_failures_max = num_failures_max, + + .ion_source_amplitude = ion_source_amplitude, + .ion_source_sigma = ion_source_sigma, + .ion_source_temp = ion_source_temp, + + .mcB = mcB, + .gamma = gamma, + .Z_m = Z_m, + .Z_min = Z_min, + .Z_max = Z_max, + }; + + // Populate a couple more values in the context. + ctx.psi_max = psi_RZ(ctx.RatZeq0, 0., &ctx); + ctx.psi_min = psi_RZ(ctx.RatZeq0 / 10, 0., &ctx); + ctx.z_min = z_psiZ(ctx.psi_max, ctx.Z_min, &ctx); + ctx.z_max = z_psiZ(ctx.psi_max, ctx.Z_max, &ctx); + + return ctx; +} + +void +release_ctx(struct gk_mirror_ctx *ctx) +{ + gkyl_free(ctx->poa_phases); +} + +void +calc_integrated_diagnostics(struct gkyl_tm_trigger *iot, gkyl_gyrokinetic_app *app, + double t_curr, bool force_calc, double dt) +{ + if (gkyl_tm_trigger_check_and_bump(iot, t_curr) || force_calc) { + gkyl_gyrokinetic_app_calc_field_energy(app, t_curr); + gkyl_gyrokinetic_app_calc_integrated_mom(app, t_curr); + + if (!(dt < 0.0) ) + gkyl_gyrokinetic_app_save_dt(app, t_curr, dt); + } +} + +void +write_data(struct gkyl_tm_trigger *iot_conf, struct gkyl_tm_trigger *iot_phase, + gkyl_gyrokinetic_app *app, double t_curr, bool force_write) +{ + bool trig_now_conf = gkyl_tm_trigger_check_and_bump(iot_conf, t_curr); + if (trig_now_conf || force_write) { + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; + gkyl_gyrokinetic_app_write_conf(app, t_curr, frame); + + gkyl_gyrokinetic_app_write_field_energy(app); + gkyl_gyrokinetic_app_write_integrated_mom(app); + gkyl_gyrokinetic_app_write_dt(app); + } + + bool trig_now_phase = gkyl_tm_trigger_check_and_bump(iot_phase, t_curr); + if (trig_now_phase || force_write) { + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; + + gkyl_gyrokinetic_app_write_phase(app, t_curr, frame); + } +} + +struct time_frame_state { + double t_curr; // Current simulation time. + double t_end; // End time of current phase. + int frame_curr; // Current frame. + int num_frames; // Number of frames at the end of current phase. +}; + +void reset_io_triggers(struct gk_mirror_ctx *ctx, struct time_frame_state *tfs, + struct gkyl_tm_trigger *trig_write_conf, struct gkyl_tm_trigger *trig_write_phase, + struct gkyl_tm_trigger *trig_calc_intdiag) +{ + // Reset I/O triggers: + double t_curr = tfs->t_curr; + double t_end = tfs->t_end; + int frame_curr = tfs->frame_curr; + int num_frames = tfs->num_frames; + int num_int_diag_calc = ctx->int_diag_calc_freq * num_frames; + + // Prevent division by zero when frame_curr equals num_frames + int frames_remaining = num_frames - frame_curr; + double time_remaining = t_end - t_curr; + + trig_write_conf->dt = time_remaining / frames_remaining; + trig_write_conf->tcurr = t_curr; + trig_write_conf->curr = frame_curr; + + trig_write_phase->dt = time_remaining / (ctx->write_phase_freq * frames_remaining); + trig_write_phase->tcurr = t_curr; + trig_write_phase->curr = frame_curr; + + int diag_frames = GKYL_MAX2(frames_remaining, + (num_int_diag_calc / num_frames) * frames_remaining); + trig_calc_intdiag->dt = time_remaining / diag_frames; + trig_calc_intdiag->tcurr = t_curr; + trig_calc_intdiag->curr = frame_curr; +} + +void run_phase(gkyl_gyrokinetic_app *app, struct gk_mirror_ctx *ctx, double num_steps, + struct gkyl_tm_trigger *trig_write_conf, struct gkyl_tm_trigger *trig_write_phase, + struct gkyl_tm_trigger *trig_calc_intdiag, struct time_frame_state *tfs, + struct gk_poa_phase_params *pparams) +{ + tfs->t_end = tfs->t_curr + pparams->duration; + tfs->num_frames = tfs->frame_curr + pparams->num_frames; + + // Run an OAP or FDP. + double t_curr = tfs->t_curr; + double t_end = tfs->t_end; + + // Reset I/O triggers: + reset_io_triggers(ctx, tfs, trig_write_conf, trig_write_phase, trig_calc_intdiag); + + // Reset simulation parameters and function pointers. + struct gkyl_gyrokinetic_collisionless collisionless_inp = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = pparams->alpha, + }; + struct gkyl_gyrokinetic_fdot_multiplier fdot_mult_inp = { + .num_multipliers = 1, + .multiplier[0] = { + .type = pparams->fdot_mult_type, + .cellwise_const = true, + .write_diagnostics = true, + } + }; + struct gkyl_gyrokinetic_field field_inp = { + .gkfield_id = GKYL_GK_FIELD_BOLTZMANN, + .electron_mass = ctx->me, + .electron_charge = ctx->qe, + .electron_temp = ctx->Te0, + .polarization_bmag = ctx->B_p, + .is_static = pparams->is_static_field, + }; + struct gkyl_gyrokinetic_positivity positivity_inp = { + .type = pparams->is_positivity_enabled? GKYL_GK_POSITIVITY_SHIFT : GKYL_GK_POSITIVITY_NONE, + .write_diagnostics = pparams->is_positivity_enabled, + }; + + gkyl_gyrokinetic_app_reset_species_collisionless(app, t_curr, "ion", collisionless_inp); + gkyl_gyrokinetic_app_reset_species_fdot_multiplier(app, t_curr, "ion", fdot_mult_inp); + gkyl_gyrokinetic_app_reset_species_positivity(app, t_curr, "ion", positivity_inp); + gkyl_gyrokinetic_app_reset_field(app, t_curr, field_inp); + + // Compute initial guess of maximum stable time-step. + double dt = t_end - t_curr; + + // Initialize small time-step check. + double dt_init = -1.0, dt_failure_tol = ctx->dt_failure_tol; + int num_failures = 0, num_failures_max = ctx->num_failures_max; + + long step = 1; + while ((t_curr < t_end) && (step <= num_steps)) { + gkyl_gyrokinetic_app_cout(app, stdout, "Taking time-step %ld at t = %g ...", step, t_curr); + dt = t_end - t_curr; // Ensure we don't step beyond t_end. + struct gkyl_update_status status = gkyl_gyrokinetic_update(app, dt); + gkyl_gyrokinetic_app_cout(app, stdout, " dt = %g\n", status.dt_actual); + + if (!status.success) { + gkyl_gyrokinetic_app_cout(app, stdout, "** Update method failed! Aborting simulation ....\n"); + break; + } + t_curr += status.dt_actual; + dt = status.dt_suggested; + + calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, t_curr >= t_end, status.dt_actual); + write_data(trig_write_conf, trig_write_phase, app, t_curr, t_curr >= t_end); + + if (dt_init < 0.0) { + dt_init = status.dt_actual; + } + else if (status.dt_actual < dt_failure_tol * dt_init) { + num_failures += 1; + + gkyl_gyrokinetic_app_cout(app, stdout, "WARNING: Time-step dt = %g", status.dt_actual); + gkyl_gyrokinetic_app_cout(app, stdout, " is below %g*dt_init ...", dt_failure_tol); + gkyl_gyrokinetic_app_cout(app, stdout, " num_failures = %d\n", num_failures); + if (num_failures >= num_failures_max) { + gkyl_gyrokinetic_app_cout(app, stdout, "ERROR: Time-step was below %g*dt_init ", + dt_failure_tol); + gkyl_gyrokinetic_app_cout(app, stdout, "%d consecutive times. Aborting simulation ....\n", + num_failures_max); + calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, true, status.dt_actual); + write_data(trig_write_conf, trig_write_phase, app, t_curr, true); + break; + } + } + else { + num_failures = 0; + } + + step += 1; + } + + tfs->t_curr = t_curr; + tfs->frame_curr = tfs->frame_curr + pparams->num_frames; +} + +int main(int argc, char **argv) +{ + struct gkyl_app_args app_args = parse_app_args(argc, argv); + +#ifdef GKYL_HAVE_MPI + if (app_args.use_mpi) MPI_Init(&argc, &argv); +#endif + + if (app_args.trace_mem) { + gkyl_cu_dev_mem_debug_set(true); + gkyl_mem_debug_set(true); + } + + struct gk_mirror_ctx ctx = create_ctx(); // Context for init functions. + + int cells_x[ctx.cdim], cells_v[ctx.vdim]; + for (int d = 0; d < ctx.cdim; d++) { + cells_x[d] = APP_ARGS_CHOOSE(app_args.xcells[d], ctx.cells[d]); + } + for (int d = 0; d < ctx.vdim; d++) { + cells_v[d] = APP_ARGS_CHOOSE(app_args.vcells[d], ctx.cells[ctx.cdim + d]); + } + + // Construct communicator for use in app. + struct gkyl_comm *comm = gkyl_gyrokinetic_comms_new(app_args.use_mpi, app_args.use_gpu, stderr); + + struct gkyl_gyrokinetic_species ion = { + .name = "ion", + .charge = ctx.qi, + .mass = ctx.mi, + .vdim = ctx.vdim, + .lower = { -1.0, 0.0 }, + .upper = { 1.0, 1.0 }, + .cells = { cells_v[0], cells_v[1] }, + .polarization_density = ctx.n0, + + .projection = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .density = eval_density_ion, + .ctx_density = &ctx, + .upar = eval_upar_ion, + .ctx_upar = &ctx, + .temp = eval_temp_ion, + .ctx_temp = &ctx, + }, + + .mapc2p = { + .mapping = mapc2p_vel_ion, + .ctx = &ctx, + }, + + .collisionless = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = 1.0, // Will be replaced below. + .write_diagnostics = true, + }, + + .time_rate_multiplier = { + .num_multipliers = 1, + .multiplier[0] = { + .type = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE, + .cellwise_const = true, + .write_diagnostics = true, + } + }, + + .collisions = { + .collision_id = GKYL_LBO_COLLISIONS, + .den_ref = ctx.n0, + .temp_ref = ctx.Te0, + .write_diagnostics = true, + }, + .source = { + .source_id = GKYL_PROJ_SOURCE, + .num_sources = 1, + .projection[0] = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .ctx_density = &ctx, + .density = eval_density_ion_source, + .ctx_upar = &ctx, + .upar = eval_upar_ion_source, + .ctx_temp = &ctx, + .temp = eval_temp_ion_source, + }, + .diagnostics = { + .num_diag_moments = 6, + .diag_moments = { GKYL_F_MOMENT_M0, GKYL_F_MOMENT_M1, GKYL_F_MOMENT_M2, GKYL_F_MOMENT_M2PAR, + GKYL_F_MOMENT_M2PERP, GKYL_F_MOMENT_BIMAXWELLIAN }, + .num_integrated_diag_moments = 1, + .integrated_diag_moments = { GKYL_F_MOMENT_M0M1M2PARM2PERP }, + }, + }, + + .bcs = { + { .dir = 0, .edge = GKYL_LOWER_EDGE, .type = GKYL_BC_GK_SPECIES_ZERO_FLUX }, + { .dir = 0, .edge = GKYL_UPPER_EDGE, .type = GKYL_BC_GK_SPECIES_ABSORB }, + { .dir = 1, .edge = GKYL_LOWER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH }, + { .dir = 1, .edge = GKYL_UPPER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH }, + }, + .write_omega_cfl = true, + .num_diag_moments = 8, + .diag_moments = { GKYL_F_MOMENT_BIMAXWELLIAN, GKYL_F_MOMENT_M0, GKYL_F_MOMENT_M1, + GKYL_F_MOMENT_M2, GKYL_F_MOMENT_M2PAR, GKYL_F_MOMENT_M2PERP, + GKYL_F_MOMENT_M3PAR, GKYL_F_MOMENT_M3PERP }, + .num_integrated_diag_moments = 1, + .integrated_diag_moments = { GKYL_F_MOMENT_M0M1M2PARM2PERP }, + .time_rate_diagnostics = true, + + .boundary_flux_diagnostics = { + .num_integrated_diag_moments = 1, + .integrated_diag_moments = { GKYL_F_MOMENT_M0M1M2PARM2PERP }, + }, + }; + struct gkyl_gyrokinetic_field field = { + .gkfield_id = GKYL_GK_FIELD_BOLTZMANN, + .electron_mass = ctx.me, + .electron_charge = ctx.qe, + .electron_temp = ctx.Te0, + .is_static = false, + }; + + struct gkyl_gk app_inp = { // GK app + .name = "gk_mirror_boltz_elc_poa_2x2v_p1", + .cdim = ctx.cdim, + .upper = { ctx.psi_max, ctx.Z_max }, + .lower = { ctx.psi_min, ctx.Z_min }, + .cells = { cells_x[0], cells_x[1] }, + .poly_order = ctx.poly_order, + .basis_type = app_args.basis_type, + + .geometry = { + .geometry_id = GKYL_GEOMETRY_MAPC2P, + .world = { 0.0 }, + .mapc2p = mapc2p, // Mapping of computational to physical space. + .c2p_ctx = &ctx, + .bfield_func = bfield_func, // Magnetic field. + .bfield_ctx = &ctx, + }, + + .num_periodic_dir = 0, + .periodic_dirs = {}, + + .num_species = 1, + .species = { ion }, + + .field = field, + + .parallelism = { + .use_gpu = app_args.use_gpu, + .cuts = { app_args.cuts[0], app_args.cuts[1] }, + .comm = comm, + }, + }; + + // Set app output name from the executable name (argv[0]). + snprintf(app_inp.name, sizeof(app_inp.name), "%s", app_args.app_name); + + // Create app object. + gkyl_gyrokinetic_app *app = gkyl_gyrokinetic_app_new(&app_inp); + + // Triggers for IO. + struct gkyl_tm_trigger trig_write_conf, trig_write_phase, trig_calc_intdiag; + + struct time_frame_state tfs = { + .t_curr = 0.0, // Initial simulation time. + .frame_curr = 0, // Initial frame. + .t_end = ctx.poa_phases[0].duration, // Final time of 1st phase. + .num_frames = ctx.poa_phases[0].num_frames, // Number of frames in 1st phase. + }; + + int phase_idx_init = 0, phase_idx_end = ctx.num_phases; // Initial and final phase index. + if (app_args.is_restart) { + struct gkyl_app_restart_status status = gkyl_gyrokinetic_app_read_from_frame(app, + app_args.restart_frame); + + if (status.io_status != GKYL_ARRAY_RIO_SUCCESS) { + gkyl_gyrokinetic_app_cout(app, stderr, "*** Failed to read restart file! (%s)\n", + gkyl_array_rio_status_msg(status.io_status)); + goto freeresources; + } + + tfs.frame_curr = status.frame; + tfs.t_curr = status.stime; + + // Find out what phase we are in. + double time_count = 0.0; + int frame_count = 0; + int pit_curr = 0; + for (int pit = 0; pit < ctx.num_phases; pit++) { + time_count += ctx.poa_phases[pit].duration; + frame_count += ctx.poa_phases[pit].num_frames; + if ((tfs.t_curr <= time_count) && (tfs.frame_curr <= frame_count)) { + pit_curr = pit; + break; + } + } + ; + phase_idx_init = pit_curr; + + // Change the duration and number frames so this phase reaches the expected + // time and number of frames and not beyond. + struct gk_poa_phase_params *pparams = &ctx.poa_phases[phase_idx_init]; + pparams->num_frames = frame_count - tfs.frame_curr; + pparams->duration = time_count - tfs.t_curr; + + gkyl_gyrokinetic_app_cout(app, stdout, "Restarting from frame %d", tfs.frame_curr); + gkyl_gyrokinetic_app_cout(app, stdout, " at time = %g\n", tfs.t_curr); + } + else { + gkyl_gyrokinetic_app_apply_ic(app, tfs.t_curr); + + // Write out ICs. + reset_io_triggers(&ctx, &tfs, &trig_write_conf, &trig_write_phase, &trig_calc_intdiag); + + calc_integrated_diagnostics(&trig_calc_intdiag, app, tfs.t_curr, true, -1.0); + write_data(&trig_write_conf, &trig_write_phase, app, tfs.t_curr, true); + } + + if (app_args.num_steps != INT_MAX) + phase_idx_end = 1; + + // Loop over number of number of phases; + for (int pit = phase_idx_init; pit < phase_idx_end; pit++) { + gkyl_gyrokinetic_app_cout(app, stdout, "\nRunning phase %d @ t = %.9e ... \n", pit, tfs.t_curr); + struct gk_poa_phase_params *phase_params = &ctx.poa_phases[pit]; + run_phase(app, &ctx, app_args.num_steps, &trig_write_conf, &trig_write_phase, + &trig_calc_intdiag, &tfs, phase_params); + } + + gkyl_gyrokinetic_app_stat_write(app); + + struct gkyl_gyrokinetic_stat stat = gkyl_gyrokinetic_app_stat(app); // fetch simulation statistics + gkyl_gyrokinetic_app_cout(app, stdout, "\n"); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of update calls %ld\n", stat.nup); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of forward-Euler calls %ld\n", stat.nfeuler); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of RK stage-2 failures %ld\n", stat.nstage_2_fail); + if (stat.nstage_2_fail > 0) { + gkyl_gyrokinetic_app_cout(app, stdout, "Max rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[1]); + gkyl_gyrokinetic_app_cout(app, stdout, "Min rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[0]); + } + gkyl_gyrokinetic_app_cout(app, stdout, "Number of RK stage-3 failures %ld\n", stat.nstage_3_fail); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of write calls %ld\n", stat.n_io); + gkyl_gyrokinetic_app_print_timings(app, stdout); + +freeresources: + // simulation complete, free app + gkyl_gyrokinetic_app_release(app); + gkyl_gyrokinetic_comms_release(comm); + release_ctx(&ctx); + +#ifdef GKYL_HAVE_MPI + if (app_args.use_mpi) + MPI_Finalize(); +#endif + return 0; +} diff --git a/gyrokinetic/creg/rt_gk_mirror_kinetic_elc_poa_1x2v_p1.c b/gyrokinetic/creg/rt_gk_mirror_kinetic_elc_poa_1x2v_p1.c new file mode 100644 index 0000000000..2f24279362 --- /dev/null +++ b/gyrokinetic/creg/rt_gk_mirror_kinetic_elc_poa_1x2v_p1.c @@ -0,0 +1,1276 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +// State of the pseudo orbit-averaged integrator. +enum gk_poa_state { + GK_POA_NONE = 0, // Haven't started. + GK_POA_OAP, // Orbit averaged phase. + GK_POA_FDP, // Full dynamics phase. + GK_POA_COMPLETED, // Finished simulation. +}; + +struct gk_poa_phase_params { + enum gk_poa_state phase; // Type of phase. + int num_frames; // Number of frames. + double duration; // Duration. + double alpha; // Factor multiplying collisionless terms. + bool is_static_field; // Whether to evolve the field. + bool is_positivity_enabled; // Whether positivity is enabled. + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type; // Type of df/dt multipler. +}; + +// Define the context of the simulation. This is basically all the globals +struct gk_mirror_ctx { + int cdim, vdim; // Dimensionality. + + // Plasma parameters + double mi; + double qi; + double me; + double qe; + double Te0; + double n0; + double B_p; + double beta; + double tau; + double Ti0; + double kperpRhos; + // Parameters controlling initial conditions. + double alim; + double alphaIC0; + double alphaIC1; + double nuFrac; + // Electron-electron collision freq. + double logLambdaElc; + double nuElc; + // Ion-ion collision freq. + double logLambdaIon; + double nuIon; + // Thermal speeds. + double vti; + double vte; + double c_s; + // Gyrofrequencies and gyroradii. + double omega_ci; + double rho_s; + double kperp; // Perpendicular wavenumber in SI units. + double RatZeq0; // Radius of the field line at Z=0. + // Axial coordinate Z extents. Endure that Z=0 is not on + double Z_min; + double Z_max; + double z_min; + double z_max; + double psi_eval; + double psi_in; + double z_in; + // Magnetic equilibrium model. + double mcB; + double gamma; + double Z_m; + // Bananna tip info. Hardcoad to avoid dependency on ctx + double B_bt; + double R_bt; + double Z_bt; + double z_bt; + double R_m; + double B_m; + double z_m; + // Physics parameters at mirror throat + double n_m; + double Te_m; + double Ti_m; + double cs_m; + // Source parameters + double NSrcIon; + double lineLengthSrcIon; + double sigSrcIon; + double NSrcFloorIon; + double TSrc0Ion; + double TSrcFloorIon; + double NSrcElc; + double lineLengthSrcElc; + double sigSrcElc; + double NSrcFloorElc; + double TSrc0Elc; + double TSrcFloorElc; + double alpha; // Multirate factor. + // Grid parameters + double vpar_max_ion; + double vpar_max_elc; + double mu_max_ion; + double mu_max_elc; + int Nz; + int Nvpar; + int Nmu; + int cells[GKYL_MAX_DIM]; // Number of cells in all directions. + int poly_order; + + double t_end; // End time. + int num_frames; // Number of output frames. + int num_phases; // Number of phases. + struct gk_poa_phase_params *poa_phases; // Phases to run. + double write_phase_freq; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). + double int_diag_calc_freq; // Frequency of calculating integrated diagnostics (as a factor of num_frames). + double dt_failure_tol; // Minimum allowable fraction of initial time-step. + int num_failures_max; // Maximum allowable number of consecutive small time-steps. +}; + +double +psi_RZ(double RIn, double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double mcB = app->mcB; + double gamma = app->gamma; + double Z_m = app->Z_m; + double psi = 0.5 * pow(RIn, 2.) * mcB * + (1. / (M_PI * gamma * (1. + pow((ZIn - Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn + Z_m) / gamma, 2.)))); + return psi; +} + +double +R_psiZ(double psiIn, double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double Rout = sqrt(2.0 * psiIn / (app->mcB * + (1.0 / (M_PI * app->gamma * (1.0 + pow((ZIn - app->Z_m) / app->gamma, 2.))) + + 1.0 / (M_PI * app->gamma * (1.0 + pow((ZIn + app->Z_m) / app->gamma, 2.)))))); + return Rout; +} + +void +Bfield_psiZ(double psiIn, double ZIn, void *ctx, double *BRad, double *BZ, double *Bmag) +{ + struct gk_mirror_ctx *app = ctx; + double Rcoord = R_psiZ(psiIn, ZIn, ctx); + double mcB = app->mcB; + double gamma = app->gamma; + double Z_m = app->Z_m; + *BRad = -(1.0 / 2.0) * Rcoord * mcB * + (-2.0 * (ZIn - Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn - Z_m) / gamma, 2.), 2.))) - + 2.0 * (ZIn + Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn + Z_m) / gamma, 2.), 2.)))); + *BZ = mcB * + (1.0 / (M_PI * gamma * (1.0 + pow((ZIn - Z_m) / gamma, 2.))) + + 1.0 / (M_PI * gamma * (1.0 + pow((ZIn + Z_m) / gamma, 2.)))); + *Bmag = sqrt(pow(*BRad, 2) + pow(*BZ, 2)); +} + +double +integrand_z_psiZ(double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = app->psi_in; + double BRad, BZ, Bmag; + Bfield_psiZ(psi, ZIn, ctx, &BRad, &BZ, &Bmag); + return Bmag / BZ; +} + +double +z_psiZ(double psiIn, double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + app->psi_in = psiIn; + double eps = 0.0; + struct gkyl_qr_res integral; + if (eps <= ZIn) { + integral = gkyl_dbl_exp(integrand_z_psiZ, ctx, eps, ZIn, 7, 1e-14); + } + else { + integral = gkyl_dbl_exp(integrand_z_psiZ, ctx, ZIn, eps, 7, 1e-14); + integral.res = -integral.res; + } + return integral.res; +} + +// Invert z(Z) via root-finding. +double +root_Z_psiz(double Z, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + return app->z_in - z_psiZ(app->psi_in, Z, ctx); +} + +double +Z_psiz(double psiIn, double zIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double maxL = app->Z_max - app->Z_min; + double eps = maxL / app->Nz; // Interestingly using a smaller eps yields larger errors in some geo quantities. + app->psi_in = psiIn; + app->z_in = zIn; + struct gkyl_qr_res Zout; + if (zIn >= 0.0) { + double fl = root_Z_psiz(-eps, ctx); + double fr = root_Z_psiz(app->Z_max + eps, ctx); + Zout = gkyl_ridders(root_Z_psiz, ctx, -eps, app->Z_max + eps, fl, fr, 1000, 1e-14); + } + else { + double fl = root_Z_psiz(app->Z_min - eps, ctx); + double fr = root_Z_psiz(eps, ctx); + Zout = gkyl_ridders(root_Z_psiz, ctx, app->Z_min - eps, eps, fl, fr, 1000, 1e-14); + } + return Zout.res; +} + +// -- Source functions. +void +eval_density_elc_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + double Z = Z_psiz(psi, z, ctx); // Cylindrical axial coordinate. + double NSrc = app->NSrcElc; + double zSrc = app->lineLengthSrcElc; + double sigSrc = app->sigSrcElc; + double NSrcFloor = app->NSrcFloorElc; + if (fabs(Z) <= app->Z_m) { + fout[0] = fmax(NSrcFloor, (NSrc / sqrt(2.0 * M_PI * pow(sigSrc, 2.))) * + exp(-1 * pow((z - zSrc), 2) / (2.0 * pow(sigSrc, 2.)))); + } + else { + fout[0] = 1e-16; + } +} + +void +eval_upar_elc_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + fout[0] = 0.0; +} + +void +eval_temp_elc_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + double sigSrc = app->sigSrcElc; + double TSrc0 = app->TSrc0Elc; + double Tfloor = app->TSrcFloorElc; + if (fabs(z) <= 2.0 * sigSrc) { + fout[0] = TSrc0; + } + else { + fout[0] = Tfloor; + } +} + +void +eval_density_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + double Z = Z_psiz(psi, z, ctx); // Cylindrical axial coordinate. + double NSrc = app->NSrcIon; + double zSrc = app->lineLengthSrcIon; + double sigSrc = app->sigSrcIon; + double NSrcFloor = app->NSrcFloorIon; + if (fabs(Z) <= app->Z_m) { + fout[0] = fmax(NSrcFloor, (NSrc / sqrt(2.0 * M_PI * pow(sigSrc, 2))) * + exp(-1 * pow((z - zSrc), 2) / (2.0 * pow(sigSrc, 2)))); + } + else { + fout[0] = 1e-16; + } +} + +void +eval_upar_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + fout[0] = 0.0; +} + +void +eval_temp_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + double sigSrc = app->sigSrcIon; + double TSrc0 = app->TSrc0Ion; + double Tfloor = app->TSrcFloorIon; + if (fabs(z) <= 2.0 * sigSrc) { + fout[0] = TSrc0; + } + else { + fout[0] = Tfloor; + } +} + +// Electrons initial conditions +void +eval_density_elc(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + double Z = Z_psiz(psi, z, ctx); // Cylindrical axial coordinate. + double R = R_psiZ(psi, Z, ctx); // Cylindrical radial coordinate. + double BRad, BZ, Bmag; + Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); + if (fabs(Z) <= app->Z_bt) { + fout[0] = app->n0 * pow((1.0 - pow((R - app->R_bt) / app->alim, 2.)), app->alphaIC0 / 2.); + } + else if (fabs(Z) <= app->Z_m) { + fout[0] = app->n0 * pow((1.0 - pow((R - app->R_bt) / app->alim, 2.)), app->alphaIC1 / 2.); + } + else { + fout[0] = app->n_m * sqrt(Bmag / app->B_m); + } +} + +void +eval_upar_elc(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + if (fabs(z) <= app->z_m) { + fout[0] = 0.0; + } + else if (z > app->z_m) { + fout[0] = app->cs_m * (z - app->z_m); + } + else { + fout[0] = app->cs_m * (z + app->z_m); + } +} + +void +eval_temp_elc(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + double Z = Z_psiz(psi, z, ctx); // Cylindrical axial coordinate. + double R = R_psiZ(psi, Z, ctx); // Cylindrical radial coordinate. + double BRad, BZ, Bmag; + Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); + if (fabs(Z) <= app->Z_bt) { + fout[0] = app->Te0 * pow((1.0 - pow((R - app->R_bt) / app->alim, 2.)), app->alphaIC0 / 2.); + } + else if (fabs(Z) <= app->Z_m) { + fout[0] = app->Te0 * pow((1.0 - pow((R - app->R_bt) / app->alim, 2.)), app->alphaIC1 / 2.); + } + else { + fout[0] = app->Te_m * sqrt(Bmag / app->B_m); + } +} + +// Ion initial conditions +void +eval_density(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + double Z = Z_psiz(psi, z, ctx); // Cylindrical axial coordinate. + double R = R_psiZ(psi, Z, ctx); // Cylindrical radial coordinate. + double BRad, BZ, Bmag; + Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); + if (fabs(Z) <= app->Z_bt) { + fout[0] = app->n0 * pow(1.0 - pow((R - app->R_bt) / app->alim, 2), app->alphaIC0 / 2); + } + else if (fabs(Z) <= app->Z_m) { + fout[0] = app->n0 * pow(1.0 - pow((R - app->R_bt) / app->alim, 2), app->alphaIC1 / 2); + } + else { + fout[0] = app->n_m * sqrt(Bmag / app->B_m); + } +} + +void +eval_upar(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + if (fabs(z) <= app->z_m) { + fout[0] = 0.0; + } + else if (z > app->z_m) { + fout[0] = app->cs_m * (z - app->z_m); // * (z - / app->z_m); + } + else { + fout[0] = app->cs_m * (z + app->z_m); // * (z + app->z_m) / app->z_m; + } +} + +void +eval_temp_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double z = xn[0]; + double Z = Z_psiz(psi, z, ctx); // Cylindrical axial coordinate. + double R = R_psiZ(psi, Z, ctx); // Cylindrical radial coordinate. + double BRad, BZ, Bmag; + Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); + if (fabs(Z) <= app->Z_bt) { + fout[0] = app->Ti0 * pow((1.0 - pow((R - app->R_bt) / app->alim, 2)), app->alphaIC0 / 2); + } + else if (fabs(Z) <= app->Z_m) { + fout[0] = app->Ti0 * pow((1.0 - pow((R - app->R_bt) / app->alim, 2)), app->alphaIC1 / 2); + } + else { + fout[0] = app->Ti_m * sqrt(Bmag / app->B_m); + } +} + +// Potential initial condition +void +eval_potential(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xn[0]; + double z_m = 0.98; + double z_max = app->z_max; + double sigma = 0.2 * z_m; + double center_potential = 8.0 * app->Te0 / app->qi; + if (fabs(z) <= sigma) { + fout[0] = center_potential; + } + else { + fout[0] = center_potential * (1 - (fabs(z) - sigma) / (z_max - sigma)); + } +} + +// Evaluate collision frequencies +void +evalNuElc(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->nuElc; +} + +void +evalNuIon(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->nuIon; +} + +// Geometry evaluation functions for the gk app +// mapc2p must assume a 3d input xc +void +mapc2p(double t, const double *xc, double *GKYL_RESTRICT xp, void *ctx) +{ + double psi = xc[0]; + double theta = xc[1]; + double z = xc[2]; + + double Z = Z_psiz(psi, z, ctx); + double R = R_psiZ(psi, Z, ctx); + + // Cartesian coordinates on plane perpendicular to Z axis. + double x = R * cos(theta); + double y = R * sin(theta); + xp[0] = x; + xp[1] = y; + xp[2] = Z; +} + +// bmag_func must assume a 3d input xc +void +bmag_func(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xc[2]; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double Z = Z_psiz(psi, z, ctx); + double BRad, BZ, Bmag; + Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); + fout[0] = Bmag; +} + +// bfield_func must assume a 3d input xc +void +bfield_func(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xc[2]; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double Z = Z_psiz(psi, z, ctx); + double BRad, BZ, Bmag; + Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); + + double phi = xc[1]; + // zc are computational coords. + // Set Cartesian components of magnetic field. + fout[0] = BRad * cos(phi); + fout[1] = BRad * sin(phi); + fout[2] = BZ; +} + +struct gk_mirror_ctx +create_ctx(void) +{ + int cdim = 1, vdim = 2; // Dimensionality. + + // Universal constant parameters. + double eps0 = GKYL_EPSILON0; + double mu0 = GKYL_MU0; // Not sure if this is right + double eV = GKYL_ELEMENTARY_CHARGE; + double mp = GKYL_PROTON_MASS; // ion mass + double me = GKYL_ELECTRON_MASS; + double qi = eV; // ion charge + double qe = -eV; // electron charge + + // Plasma parameters. + double mi = 2.014 * mp; + double Te0 = 940 * eV; + double n0 = 3e19; + double B_p = 0.53; + double beta = 0.4; + double tau = pow(B_p, 2.) * beta / (2.0 * mu0 * n0 * Te0) - 1.; + double Ti0 = tau * Te0; + double kperpRhos = 0.1; + + // Parameters controlling initial conditions. + double alim = 0.125; + double alphaIC0 = 2; + double alphaIC1 = 10; + + double nuFrac = 1.0; + // Electron-electron collision freq. + double logLambdaElc = 6.6 - 0.5 * log(n0 / 1e20) + 1.5 * log(Te0 / eV); + double nuElc = nuFrac * logLambdaElc * pow(eV, 4.) * n0 / + (6. * sqrt(2.) * pow(M_PI, 3. / 2.) * pow(eps0, 2.) * sqrt(me) * pow(Te0, 3. / 2.)); + // Ion-ion collision freq. + double logLambdaIon = 6.6 - 0.5 * log(n0 / 1e20) + 1.5 * log(Ti0 / eV); + double nuIon = nuFrac * logLambdaIon * pow(eV, 4.) * n0 / + (12 * pow(M_PI, 3. / 2.) * pow(eps0, 2.) * sqrt(mi) * pow(Ti0, 3. / 2.)); + + // Thermal speeds. + double vti = sqrt(Ti0 / mi); + double vte = sqrt(Te0 / me); + double c_s = sqrt(Te0 / mi); + + // Gyrofrequencies and gyroradii. + double omega_ci = eV * B_p / mi; + double rho_s = c_s / omega_ci; + + // Perpendicular wavenumber in SI units: + double kperp = kperpRhos / rho_s; + + // Geometry parameters. + double RatZeq0 = 0.10; // Radius of the field line at Z=0. + // Axial coordinate Z extents. Endure that Z=0 is not on + // the boundary of a cell (due to AD errors). + double Z_min = -2.5; + double Z_max = 2.5; + + // Parameters controlling the magnetic equilibrium model. + double mcB = 6.51292; + double gamma = 0.124904; + double Z_m = 0.98; + + // Source parameters + double NSrcIon = 3.1715e23 / 8.0; + double lineLengthSrcIon = 0.0; + double sigSrcIon = Z_m / 4.0; + double NSrcFloorIon = 0.05 * NSrcIon; + double TSrc0Ion = Ti0 * 1.25; + double TSrcFloorIon = TSrc0Ion / 8.0; + double NSrcElc = NSrcIon; + double lineLengthSrcElc = lineLengthSrcIon; + double sigSrcElc = sigSrcIon; + double NSrcFloorElc = NSrcFloorIon; + double TSrc0Elc = TSrc0Ion / tau; + double TSrcFloorElc = TSrcFloorIon / tau; + + // Bananna tip info. Hardcoad to avoid dependency on ctx + double B_bt = 1.058278; + double R_bt = 0.071022; + double Z_bt = 0.467101; + double z_bt = 0.468243; + double R_m = 0.017845; + double B_m = 16.662396; + double z_m = 0.982544; + + // Physics parameters at mirror throat + double n_m = 1.105617e19; + double Te_m = 346.426583 * eV; + double Ti_m = 3081.437703 * eV; + double cs_m = 4.037740e5; + + double alpha = 0.01; // Multirate factor. + + // Grid parameters + double vpar_max_elc = 20 * vte; + double mu_max_elc = me * pow(3. * vte, 2.) / (2. * B_p); + double vpar_max_ion = 20 * vti; + double mu_max_ion = mi * pow(3. * vti, 2.) / (2. * B_p); + // Keep the default regression small enough for the 90 s regression-test + // budget while retaining both kinetic species and every POA phase. + int Nz = 16; + int Nvpar = 16; + int Nmu = 8; + int poly_order = 1; + + // Factor multiplying collisionless terms. + double alpha_oap = 0.01; + double alpha_fdp = 1.0; + // Duration of each phase. + double tau_oap = 1e-7; + double tau_fdp = 3e-10; + double tau_fdp_extra = 2 * tau_fdp; + int num_cycles = 2; // Number of OAP+FDP cycles to run. + + // Frame counts for each phase type (specified independently) + int num_frames_oap = 1; // Frames per OAP phase + int num_frames_fdp = 1; // Frames per FDP phase + int num_frames_fdp_extra = 2 * num_frames_fdp; // Frames for the extra FDP phase + + // Whether to evolve the field. + bool is_static_field_oap = true; + bool is_static_field_fdp = false; + // Whether to enable positivity. + bool is_positivity_enabled_oap = false; + bool is_positivity_enabled_fdp = true; + // Type of df/dt multipler. + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_oap = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE; + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_fdp = GKYL_GK_FDOT_MULTIPLIER_NONE; + + // Calculate phase structure + double t_end = (tau_oap + tau_fdp) * num_cycles + tau_fdp_extra; + double tau_pair = tau_oap + tau_fdp; // Duration of an OAP+FDP pair. + int num_phases = 2 * num_cycles + 1; + int num_frames = num_cycles * (num_frames_oap + num_frames_fdp) + num_frames_fdp_extra; + + struct gk_poa_phase_params *poa_phases = gkyl_calloc(num_phases, + sizeof(struct gk_poa_phase_params)); + for (int i = 0; i < (num_phases - 1) / 2; i++) { + // OAPs. + poa_phases[2 * i].phase = GK_POA_OAP; + poa_phases[2 * i].num_frames = num_frames_oap; + poa_phases[2 * i].duration = tau_oap; + poa_phases[2 * i].alpha = alpha_oap; + poa_phases[2 * i].is_static_field = is_static_field_oap; + poa_phases[2 * i].fdot_mult_type = fdot_mult_type_oap; + poa_phases[2 * i].is_positivity_enabled = is_positivity_enabled_oap; + + // FDPs. + poa_phases[2 * i + 1].phase = GK_POA_FDP; + poa_phases[2 * i + 1].num_frames = num_frames_fdp; + poa_phases[2 * i + 1].duration = tau_fdp; + poa_phases[2 * i + 1].alpha = alpha_fdp; + poa_phases[2 * i + 1].is_static_field = is_static_field_fdp; + poa_phases[2 * i + 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[2 * i + 1].is_positivity_enabled = is_positivity_enabled_fdp; + } + // Add an extra, longer FDP. + poa_phases[num_phases - 1].phase = GK_POA_FDP; + poa_phases[num_phases - 1].num_frames = num_frames_fdp_extra; + poa_phases[num_phases - 1].duration = tau_fdp_extra; + poa_phases[num_phases - 1].alpha = alpha_fdp; + poa_phases[num_phases - 1].is_static_field = is_static_field_fdp; + poa_phases[num_phases - 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[num_phases - 1].is_positivity_enabled = is_positivity_enabled_fdp; + + double write_phase_freq = 0.5; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). + double int_diag_calc_freq = 5; // Frequency of calculating integrated diagnostics (as a factor of num_frames). + double dt_failure_tol = 1.0e-4; // Minimum allowable fraction of initial time-step. + int num_failures_max = 20; // Maximum allowable number of consecutive small time-steps. + + struct gk_mirror_ctx ctx = { + .cdim = cdim, + .vdim = vdim, + .mi = mi, + .qi = qi, + .me = me, + .qe = qe, + .Te0 = Te0, + .n0 = n0, + .B_p = B_p, + .beta = beta, + .tau = tau, + .Ti0 = Ti0, + .kperpRhos = kperpRhos, + .alim = alim, + .alphaIC0 = alphaIC0, + .alphaIC1 = alphaIC1, + .nuFrac = nuFrac, + .logLambdaElc = logLambdaElc, + .nuElc = nuElc, + .logLambdaIon = logLambdaIon, + .nuIon = nuIon, + .vti = vti, + .vte = vte, + .c_s = c_s, + .omega_ci = omega_ci, + .rho_s = rho_s, + .kperp = kperp, + .RatZeq0 = RatZeq0, + .Z_min = Z_min, + .Z_max = Z_max, + .mcB = mcB, + .gamma = gamma, + .Z_m = Z_m, + .B_bt = B_bt, + .R_bt = R_bt, + .Z_bt = Z_bt, + .z_bt = z_bt, + .R_m = R_m, + .B_m = B_m, + .z_m = z_m, + .n_m = n_m, + .Te_m = Te_m, + .Ti_m = Ti_m, + .cs_m = cs_m, + .NSrcIon = NSrcIon, + .lineLengthSrcIon = lineLengthSrcIon, + .sigSrcIon = sigSrcIon, + .NSrcFloorIon = NSrcFloorIon, + .TSrc0Ion = TSrc0Ion, + .TSrcFloorIon = TSrcFloorIon, + .NSrcElc = NSrcElc, + .lineLengthSrcElc = lineLengthSrcElc, + .sigSrcElc = sigSrcElc, + .NSrcFloorElc = NSrcFloorElc, + .TSrc0Elc = TSrc0Elc, + .TSrcFloorElc = TSrcFloorElc, + .vpar_max_ion = vpar_max_ion, + .vpar_max_elc = vpar_max_elc, + .mu_max_ion = mu_max_ion, + .mu_max_elc = mu_max_elc, + .Nz = Nz, + .Nvpar = Nvpar, + .Nmu = Nmu, + .cells = { Nz, Nvpar, Nmu }, + .poly_order = poly_order, + .t_end = t_end, + .num_frames = num_frames, + .num_phases = num_phases, + .poa_phases = poa_phases, + .write_phase_freq = write_phase_freq, + .int_diag_calc_freq = int_diag_calc_freq, + .dt_failure_tol = dt_failure_tol, + .num_failures_max = num_failures_max, + }; + + // Populate a couple more values in the context. + ctx.psi_eval = psi_RZ(ctx.RatZeq0, 0., &ctx); + ctx.z_min = z_psiZ(ctx.psi_eval, ctx.Z_min, &ctx); + ctx.z_max = z_psiZ(ctx.psi_eval, ctx.Z_max, &ctx); + + return ctx; +} + +void +release_ctx(struct gk_mirror_ctx *ctx) +{ + gkyl_free(ctx->poa_phases); +} + +void +calc_integrated_diagnostics(struct gkyl_tm_trigger *iot, gkyl_gyrokinetic_app *app, + double t_curr, bool force_calc, double dt) +{ + if (gkyl_tm_trigger_check_and_bump(iot, t_curr) || force_calc) { + gkyl_gyrokinetic_app_calc_field_energy(app, t_curr); + gkyl_gyrokinetic_app_calc_integrated_mom(app, t_curr); + + if (!(dt < 0.0) ) + gkyl_gyrokinetic_app_save_dt(app, t_curr, dt); + } +} + +void +write_data(struct gkyl_tm_trigger *iot_conf, struct gkyl_tm_trigger *iot_phase, + gkyl_gyrokinetic_app *app, double t_curr, bool force_write) +{ + bool trig_now_conf = gkyl_tm_trigger_check_and_bump(iot_conf, t_curr); + if (trig_now_conf || force_write) { + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; + gkyl_gyrokinetic_app_write_conf(app, t_curr, frame); + + gkyl_gyrokinetic_app_write_field_energy(app); + gkyl_gyrokinetic_app_write_integrated_mom(app); + gkyl_gyrokinetic_app_write_dt(app); + } + + bool trig_now_phase = gkyl_tm_trigger_check_and_bump(iot_phase, t_curr); + if (trig_now_phase || force_write) { + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; + + gkyl_gyrokinetic_app_write_phase(app, t_curr, frame); + } +} + +struct time_frame_state { + double t_curr; // Current simulation time. + double t_end; // End time of current phase. + int frame_curr; // Current frame. + int num_frames; // Number of frames at the end of current phase. +}; + +void reset_io_triggers(struct gk_mirror_ctx *ctx, struct time_frame_state *tfs, + struct gkyl_tm_trigger *trig_write_conf, struct gkyl_tm_trigger *trig_write_phase, + struct gkyl_tm_trigger *trig_calc_intdiag) +{ + // Reset I/O triggers: + double t_curr = tfs->t_curr; + double t_end = tfs->t_end; + int frame_curr = tfs->frame_curr; + int num_frames = tfs->num_frames; + int num_int_diag_calc = ctx->int_diag_calc_freq * num_frames; + + // Prevent division by zero when frame_curr equals num_frames + int frames_remaining = num_frames - frame_curr; + double time_remaining = t_end - t_curr; + + trig_write_conf->dt = time_remaining / frames_remaining; + trig_write_conf->tcurr = t_curr; + trig_write_conf->curr = frame_curr; + + trig_write_phase->dt = time_remaining / (ctx->write_phase_freq * frames_remaining); + trig_write_phase->tcurr = t_curr; + trig_write_phase->curr = frame_curr; + + int diag_frames = GKYL_MAX2(frames_remaining, + (num_int_diag_calc / num_frames) * frames_remaining); + trig_calc_intdiag->dt = time_remaining / diag_frames; + trig_calc_intdiag->tcurr = t_curr; + trig_calc_intdiag->curr = frame_curr; +} + +void run_phase(gkyl_gyrokinetic_app *app, struct gk_mirror_ctx *ctx, double num_steps, + struct gkyl_tm_trigger *trig_write_conf, struct gkyl_tm_trigger *trig_write_phase, + struct gkyl_tm_trigger *trig_calc_intdiag, struct time_frame_state *tfs, + struct gk_poa_phase_params *pparams) +{ + tfs->t_end = tfs->t_curr + pparams->duration; + tfs->num_frames = tfs->frame_curr + pparams->num_frames; + + // Run an OAP or FDP. + double t_curr = tfs->t_curr; + double t_end = tfs->t_end; + + // Reset I/O triggers: + reset_io_triggers(ctx, tfs, trig_write_conf, trig_write_phase, trig_calc_intdiag); + + // Reset simulation parameters and function pointers. + struct gkyl_gyrokinetic_collisionless collisionless_inp = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = pparams->alpha, + }; + struct gkyl_gyrokinetic_fdot_multiplier fdot_mult_inp = { + .num_multipliers = 1, + .multiplier[0] = { + .type = pparams->fdot_mult_type, + .cellwise_const = true, + .write_diagnostics = true, + } + }; + struct gkyl_gyrokinetic_field field_inp = { + .polarization_bmag = ctx->B_p, + .kperpSq = pow(ctx->kperp, 2.), + .is_static = pparams->is_static_field, + }; + struct gkyl_gyrokinetic_positivity positivity_inp = { + .type = pparams->is_positivity_enabled? GKYL_GK_POSITIVITY_SHIFT : GKYL_GK_POSITIVITY_NONE, + .write_diagnostics = pparams->is_positivity_enabled, + }; + + gkyl_gyrokinetic_app_reset_species_collisionless(app, t_curr, "ion", collisionless_inp); + gkyl_gyrokinetic_app_reset_species_collisionless(app, t_curr, "elc", collisionless_inp); + gkyl_gyrokinetic_app_reset_species_fdot_multiplier(app, t_curr, "ion", fdot_mult_inp); + gkyl_gyrokinetic_app_reset_species_fdot_multiplier(app, t_curr, "elc", fdot_mult_inp); + gkyl_gyrokinetic_app_reset_species_positivity(app, t_curr, "ion", positivity_inp); + gkyl_gyrokinetic_app_reset_species_positivity(app, t_curr, "elc", positivity_inp); + gkyl_gyrokinetic_app_reset_field(app, t_curr, field_inp); + + // Compute initial guess of maximum stable time-step. + double dt = t_end - t_curr; + + // Initialize small time-step check. + double dt_init = -1.0, dt_failure_tol = ctx->dt_failure_tol; + int num_failures = 0, num_failures_max = ctx->num_failures_max; + + long step = 1; + while ((t_curr < t_end) && (step <= num_steps)) { + if (step == 1 || step % 1 == 0) + gkyl_gyrokinetic_app_cout(app, stdout, "Taking time-step at t = %g ...", t_curr); + + dt = fmin(dt, t_end - t_curr); // Don't step beyond t_end. + struct gkyl_update_status status = gkyl_gyrokinetic_update(app, dt); + + if (step == 1 || step % 1 == 0) + gkyl_gyrokinetic_app_cout(app, stdout, " dt = %g\n", status.dt_actual); + + if (!status.success) { + gkyl_gyrokinetic_app_cout(app, stdout, "** Update method failed! Aborting simulation ....\n"); + break; + } + t_curr += status.dt_actual; + dt = status.dt_suggested; + + calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, t_curr > t_end, status.dt_actual); + write_data(trig_write_conf, trig_write_phase, app, t_curr, t_curr > t_end); + + if (dt_init < 0.0) { + dt_init = status.dt_actual; + } + else if (status.dt_actual < dt_failure_tol * dt_init) { + num_failures += 1; + + gkyl_gyrokinetic_app_cout(app, stdout, "WARNING: Time-step dt = %g", status.dt_actual); + gkyl_gyrokinetic_app_cout(app, stdout, " is below %g*dt_init ...", dt_failure_tol); + gkyl_gyrokinetic_app_cout(app, stdout, " num_failures = %d\n", num_failures); + if (num_failures >= num_failures_max) { + gkyl_gyrokinetic_app_cout(app, stdout, "ERROR: Time-step was below %g*dt_init ", + dt_failure_tol); + gkyl_gyrokinetic_app_cout(app, stdout, "%d consecutive times. Aborting simulation ....\n", + num_failures_max); + calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, true, status.dt_actual); + write_data(trig_write_conf, trig_write_phase, app, t_curr, true); + break; + } + } + else { + num_failures = 0; + } + + step += 1; + } + + tfs->t_curr = t_curr; + tfs->frame_curr = tfs->frame_curr + pparams->num_frames; +} + +int main(int argc, char **argv) +{ + struct gkyl_app_args app_args = parse_app_args(argc, argv); + +#ifdef GKYL_HAVE_MPI + if (app_args.use_mpi) MPI_Init(&argc, &argv); +#endif + + if (app_args.trace_mem) { + gkyl_cu_dev_mem_debug_set(true); + gkyl_mem_debug_set(true); + } + + struct gk_mirror_ctx ctx = create_ctx(); // Context for init functions. + + int cells_x[ctx.cdim], cells_v[ctx.vdim]; + for (int d = 0; d < ctx.cdim; d++) { + cells_x[d] = APP_ARGS_CHOOSE(app_args.xcells[d], ctx.cells[d]); + } + for (int d = 0; d < ctx.vdim; d++) { + cells_v[d] = APP_ARGS_CHOOSE(app_args.vcells[d], ctx.cells[ctx.cdim + d]); + } + + // Construct communicator for use in app. + struct gkyl_comm *comm = gkyl_gyrokinetic_comms_new(app_args.use_mpi, app_args.use_gpu, stderr); + + struct gkyl_gyrokinetic_species elc = { + .name = "elc", + .charge = ctx.qe, + .mass = ctx.me, + .vdim = ctx.vdim, + .lower = { -ctx.vpar_max_elc, 0.0 }, + .upper = { ctx.vpar_max_elc, ctx.mu_max_elc }, + .cells = { cells_v[0], cells_v[1] }, + + .polarization_density = ctx.n0, + + .projection = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .ctx_density = &ctx, + .density = eval_density_elc, + .ctx_upar = &ctx, + .upar = eval_upar_elc, + .ctx_temp = &ctx, + .temp = eval_temp_elc, + }, + + .collisionless = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = 1.0, // Will be replaced below. + }, + + .collisions = { + .collision_id = GKYL_LBO_COLLISIONS, + .den_ref = ctx.n0, + .temp_ref = ctx.Te0, + .num_cross_collisions = 1, + .collide_with = { "ion" }, + }, + + .source = { + .source_id = GKYL_PROJ_SOURCE, + .num_sources = 1, + .projection[0] = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .ctx_density = &ctx, + .density = eval_density_elc_source, + .ctx_upar = &ctx, + .upar = eval_upar_elc_source, + .ctx_temp = &ctx, + .temp = eval_temp_elc_source, + }, + }, + + .time_rate_multiplier = { + .num_multipliers = 1, + .multiplier[0] = { + .type = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE, + .cellwise_const = true, + .write_diagnostics = true, + }, + }, + + .positivity = { + .type = GKYL_GK_POSITIVITY_SHIFT, + .write_diagnostics = true, + }, + + .bcs = { + { .dir = 0, .edge = GKYL_LOWER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + { .dir = 0, .edge = GKYL_UPPER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + }, + + .write_omega_cfl = true, + .num_diag_moments = 8, + .diag_moments = { GKYL_F_MOMENT_M0, GKYL_F_MOMENT_M1, GKYL_F_MOMENT_M2, GKYL_F_MOMENT_M2PAR, + GKYL_F_MOMENT_M2PERP, GKYL_F_MOMENT_M3PAR, GKYL_F_MOMENT_M3PERP, + GKYL_F_MOMENT_BIMAXWELLIAN }, + }; + + struct gkyl_gyrokinetic_species ion = { + .name = "ion", + .charge = ctx.qi, + .mass = ctx.mi, + .vdim = ctx.vdim, + .lower = { -ctx.vpar_max_ion, 0.0 }, + .upper = { ctx.vpar_max_ion, ctx.mu_max_ion }, + .cells = { cells_v[0], cells_v[1] }, + .scale_with_polarization = true, + + .polarization_density = ctx.n0, + + .projection = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .ctx_density = &ctx, + .density = eval_density, + .ctx_upar = &ctx, + .upar = eval_upar, + .ctx_temp = &ctx, + .temp = eval_temp_ion, + }, + + .collisionless = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = 1.0, // Will be replaced below. + }, + + .collisions = { + .collision_id = GKYL_LBO_COLLISIONS, + .den_ref = ctx.n0, + .temp_ref = ctx.Ti0, + .num_cross_collisions = 1, + .collide_with = { "elc" }, + }, + + .source = { + .source_id = GKYL_PROJ_SOURCE, + .num_sources = 1, + .projection[0] = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .ctx_density = &ctx, + .density = eval_density_source, + .ctx_upar = &ctx, + .upar = eval_upar_source, + .ctx_temp = &ctx, + .temp = eval_temp_ion_source, + }, + }, + + .time_rate_multiplier = { + .num_multipliers = 1, + .multiplier[0] = { + .type = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE, + .cellwise_const = true, + .write_diagnostics = true, + }, + }, + + .positivity = { + .type = GKYL_GK_POSITIVITY_SHIFT, + .write_diagnostics = true, + }, + + .bcs = { + { .dir = 0, .edge = GKYL_LOWER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + { .dir = 0, .edge = GKYL_UPPER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + }, + + .write_omega_cfl = true, + .num_diag_moments = 8, + .diag_moments = { GKYL_F_MOMENT_M0, GKYL_F_MOMENT_M1, GKYL_F_MOMENT_M2, GKYL_F_MOMENT_M2PAR, + GKYL_F_MOMENT_M2PERP, GKYL_F_MOMENT_M3PAR, GKYL_F_MOMENT_M3PERP, + GKYL_F_MOMENT_BIMAXWELLIAN }, + }; + + struct gkyl_gyrokinetic_field field = { + .polarization_bmag = ctx.B_p, // Issue here. B0 from soloviev, so not sure what to do. Ours is not constant + .kperpSq = pow(ctx.kperp, 2.), + .is_static = false, // Will be replaced below. + .polarization_potential = eval_potential, + .polarization_potential_ctx = &ctx, + }; + + // GK app + struct gkyl_gk app_inp = { + .name = "gk_mirror_kinetic_elc_poa_1x2v_p1", + .cdim = ctx.cdim, + .lower = { ctx.z_min }, + .upper = { ctx.z_max }, + .cells = { cells_x[0] }, + .poly_order = ctx.poly_order, + .basis_type = app_args.basis_type, + + .geometry = { + .geometry_id = GKYL_GEOMETRY_MAPC2P, + .world = { ctx.psi_eval, 0.0 }, + .mapc2p = mapc2p, // Mapping of computational to physical space. + .c2p_ctx = &ctx, + .bfield_func = bfield_func, // Magnetic field. + .bfield_ctx = &ctx + }, + + .num_periodic_dir = 0, + .periodic_dirs = {}, + .num_species = 2, + .species = { elc, ion }, + .field = field, + + .parallelism = { + .use_gpu = app_args.use_gpu, + .cuts = { app_args.cuts[0] }, + .comm = comm, + }, + }; + + // Set app output name from the executable name (argv[0]). + snprintf(app_inp.name, sizeof(app_inp.name), "%s", app_args.app_name); + + // Create app object. + gkyl_gyrokinetic_app *app = gkyl_gyrokinetic_app_new(&app_inp); + + // Triggers for IO. + struct gkyl_tm_trigger trig_write_conf, trig_write_phase, trig_calc_intdiag; + + struct time_frame_state tfs = { + .t_curr = 0.0, // Initial simulation time. + .frame_curr = 0, // Initial frame. + .t_end = ctx.poa_phases[0].duration, // Final time of 1st phase. + .num_frames = ctx.poa_phases[0].num_frames, // Number of frames in 1st phase. + }; + + int phase_idx_init = 0, phase_idx_end = ctx.num_phases; // Initial and final phase index. + if (app_args.is_restart) { + struct gkyl_app_restart_status status = gkyl_gyrokinetic_app_read_from_frame(app, + app_args.restart_frame); + + if (status.io_status != GKYL_ARRAY_RIO_SUCCESS) { + gkyl_gyrokinetic_app_cout(app, stderr, "*** Failed to read restart file! (%s)\n", + gkyl_array_rio_status_msg(status.io_status)); + goto freeresources; + } + + tfs.frame_curr = status.frame; + tfs.t_curr = status.stime; + + // Find out what phase we are in. + double time_count = 0.0; + int frame_count = 0; + int pit_curr = 0; + for (int pit = 0; pit < ctx.num_phases; pit++) { + time_count += ctx.poa_phases[pit].duration; + frame_count += ctx.poa_phases[pit].num_frames; + if ((tfs.t_curr <= time_count) && (tfs.frame_curr <= frame_count)) { + pit_curr = pit; + break; + } + } + ; + phase_idx_init = pit_curr; + + // Change the duration and number frames so this phase reaches the expected + // time and number of frames and not beyond. + struct gk_poa_phase_params *pparams = &ctx.poa_phases[phase_idx_init]; + pparams->num_frames = frame_count - tfs.frame_curr; + pparams->duration = time_count - tfs.t_curr; + + gkyl_gyrokinetic_app_cout(app, stdout, "Restarting from frame %d", tfs.frame_curr); + gkyl_gyrokinetic_app_cout(app, stdout, " at time = %g\n", tfs.t_curr); + } + else { + gkyl_gyrokinetic_app_apply_ic(app, tfs.t_curr); + + // Write out ICs. + reset_io_triggers(&ctx, &tfs, &trig_write_conf, &trig_write_phase, &trig_calc_intdiag); + + calc_integrated_diagnostics(&trig_calc_intdiag, app, tfs.t_curr, true, -1.0); + write_data(&trig_write_conf, &trig_write_phase, app, tfs.t_curr, true); + } + + if (app_args.num_steps != INT_MAX) + phase_idx_end = 1; + + // Loop over number of number of phases; + for (int pit = phase_idx_init; pit < phase_idx_end; pit++) { + gkyl_gyrokinetic_app_cout(app, stdout, "\nRunning phase %d @ t = %.9e ... \n", pit, tfs.t_curr); + struct gk_poa_phase_params *phase_params = &ctx.poa_phases[pit]; + run_phase(app, &ctx, app_args.num_steps, &trig_write_conf, &trig_write_phase, + &trig_calc_intdiag, &tfs, phase_params); + } + + gkyl_gyrokinetic_app_stat_write(app); + + struct gkyl_gyrokinetic_stat stat = gkyl_gyrokinetic_app_stat(app); // fetch simulation statistics + gkyl_gyrokinetic_app_cout(app, stdout, "\n"); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of update calls %ld\n", stat.nup); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of forward-Euler calls %ld\n", stat.nfeuler); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of RK stage-2 failures %ld\n", stat.nstage_2_fail); + if (stat.nstage_2_fail > 0) { + gkyl_gyrokinetic_app_cout(app, stdout, "Max rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[1]); + gkyl_gyrokinetic_app_cout(app, stdout, "Min rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[0]); + } + gkyl_gyrokinetic_app_cout(app, stdout, "Number of RK stage-3 failures %ld\n", stat.nstage_3_fail); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of write calls %ld\n", stat.n_io); + gkyl_gyrokinetic_app_print_timings(app, stdout); + +freeresources: + // simulation complete, free app + gkyl_gyrokinetic_app_release(app); + gkyl_gyrokinetic_comms_release(comm); + release_ctx(&ctx); + +#ifdef GKYL_HAVE_MPI + if (app_args.use_mpi) + MPI_Finalize(); +#endif + return 0; +} diff --git a/gyrokinetic/creg/rt_gk_mirror_tandem_boltz_elc_poa_1x2v.c b/gyrokinetic/creg/rt_gk_mirror_tandem_boltz_elc_poa_1x2v.c new file mode 100644 index 0000000000..8ed50bd2b0 --- /dev/null +++ b/gyrokinetic/creg/rt_gk_mirror_tandem_boltz_elc_poa_1x2v.c @@ -0,0 +1,953 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +// State of the pseudo orbit-averaged integrator. +enum gk_poa_state { + GK_POA_NONE = 0, // Haven't started. + GK_POA_OAP, // Orbit averaged phase. + GK_POA_FDP, // Full dynamics phase. + GK_POA_COMPLETED, // Finished simulation. +}; + +struct gk_poa_phase_params { + enum gk_poa_state phase; // Type of phase. + int num_frames; // Number of frames. + double duration; // Duration. + double alpha; // Factor multiplying collisionless terms. + bool is_static_field; // Whether to evolve the field. + bool is_positivity_enabled; // Whether positivity is enabled. + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type; // Type of df/dt multipler. +}; + +// Define the context of the simulation. This is basically all the globals +struct gk_mirror_ctx { + int cdim, vdim; // Dimensionality. + + // Plasma parameters + double mi; // Ion mass. + double me; // Electron mass. + double qi; // Ion charge. + double qe; // Electron charge. + double Te0; // Electron temperature. + double Ti0; // Ion temperature. + double n0; // Density. + double B_p; // Plasma magnetic field (mirror center). + double beta; // Plasma beta in the center. + double tau; // Temperature ratio. + + double Ti_perp0; // Reference ion perp temperature. + double Ti_par0; // Reference ion par temperature. + double cs_m; // Ion sound speed at the throat. + + double nuFrac; // Fraction multiplying collision frequency. + double logLambdaIon; // Ion Coulomb logarithm. + double nuIon; // Ion-ion collision freq. + + double vti; // Ion thermal speed. + double vte; // Electron thermal speed. + double c_s; // Ion sound speed. + double omega_ci; // Ion gyrofrequency. + double rho_s; // Ion sound gyroradius. + + double RatZeq0; // Radius of the field line at Z=0. + double Z_min; // Minimum axial coordinate Z. + double Z_max; // Maximum axial coordinate Z. + double z_min; // Minimum value of the position along the field line. + double z_max; // Maximum value of the position along the field line. + double psi_eval; // Psi (poloidal flux) of the field line. + double psi_in, z_in; // Auxiliary psi and z. + + // Magnetic equilibrium model. + double mcB; + double gamma; + double Z_m; // Axial coordinate at mirror throat. + double z_m; // Computational coordinate at mirror throat. + + // Source parameters + double NSrcIon; + double TSrc0Ion; + + // Physical velocity space limits. + double vpar_min_ion, vpar_max_ion; + double mu_max_ion; + // Computational velocity space limits. + double vpar_min_ion_c, vpar_max_ion_c; + double mu_min_ion_c, mu_max_ion_c; + + // Grid DOF. + int Nz; + int Nvpar; + int Nmu; + int cells[GKYL_MAX_DIM]; // Number of cells in all directions. + int poly_order; + + double t_end; // End time. + int num_frames; // Number of output frames. + int num_phases; // Number of phases. + struct gk_poa_phase_params *poa_phases; // Phases to run. + double write_phase_freq; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). + double int_diag_calc_freq; // Frequency of calculating integrated diagnostics (as a factor of num_frames). + double dt_failure_tol; // Minimum allowable fraction of initial time-step. + int num_failures_max; // Maximum allowable number of consecutive small time-steps. +}; + +double +psi_RZ(double RIn, double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double mcB = app->mcB; + double gamma = app->gamma; + double Z_m = app->Z_m; + double psi = 0.5 * pow(RIn, 2.) * mcB * + (1. / (M_PI * gamma * (1. + pow((ZIn - Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn + Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn - 2 * Z_m) / gamma, 2.))) + + 1. / (M_PI * gamma * (1. + pow((ZIn + 2 * Z_m) / gamma, 2.)))); + return psi; +} + +double +R_psiZ(double psiIn, double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double Rout = sqrt(2.0 * psiIn / (app->mcB * + (1.0 / (M_PI * app->gamma * (1.0 + pow((ZIn - app->Z_m) / app->gamma, 2.))) + + 1.0 / (M_PI * app->gamma * (1.0 + pow((ZIn + app->Z_m) / app->gamma, 2.))) + + 1.0 / (M_PI * app->gamma * (1.0 + pow((ZIn - 2 * app->Z_m) / app->gamma, 2.))) + + 1.0 / (M_PI * app->gamma * (1.0 + pow((ZIn + 2 * app->Z_m) / app->gamma, 2.))) + ))); + return Rout; +} + +void +Bfield_psiZ(double psiIn, double ZIn, void *ctx, double *BRad, double *BZ, double *Bmag) +{ + struct gk_mirror_ctx *app = ctx; + double Rcoord = R_psiZ(psiIn, ZIn, ctx); + double mcB = app->mcB; + double gamma = app->gamma; + double Z_m = app->Z_m; + *BRad = -(1.0 / 2.0) * Rcoord * mcB * + (-2.0 * (ZIn - Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn - Z_m) / gamma, 2.), 2.))) + + -2.0 * (ZIn + Z_m) / (M_PI * pow(gamma, 3.) * (pow(1.0 + pow((ZIn + Z_m) / gamma, 2.), 2.))) + + -2.0 * (ZIn - 2 * Z_m) / (M_PI * pow(gamma, + 3.) * (pow(1.0 + pow((ZIn - 2 * Z_m) / gamma, 2.), 2.))) + + -2.0 * (ZIn + 2 * Z_m) / (M_PI * pow(gamma, + 3.) * (pow(1.0 + pow((ZIn + 2 * Z_m) / gamma, 2.), 2.))) + ); + *BZ = mcB * + (1.0 / (M_PI * gamma * (1.0 + pow((ZIn - Z_m) / gamma, 2.))) + + 1.0 / (M_PI * gamma * (1.0 + pow((ZIn + Z_m) / gamma, 2.))) + + 1.0 / (M_PI * gamma * (1.0 + pow((ZIn - 2 * Z_m) / gamma, 2.))) + + 1.0 / (M_PI * gamma * (1.0 + pow((ZIn + 2 * Z_m) / gamma, 2.))) + ); + *Bmag = sqrt(pow(*BRad, 2) + pow(*BZ, 2)); +} + +double +integrand_z_psiZ(double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double psi = app->psi_in; + double BRad, BZ, Bmag; + Bfield_psiZ(psi, ZIn, ctx, &BRad, &BZ, &Bmag); + return Bmag / BZ; +} + +double +z_psiZ(double psiIn, double ZIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + app->psi_in = psiIn; + double eps = 0.0; + struct gkyl_qr_res integral; + if (eps <= ZIn) { + integral = gkyl_dbl_exp(integrand_z_psiZ, ctx, eps, ZIn, 7, 1e-14); + } + else { + integral = gkyl_dbl_exp(integrand_z_psiZ, ctx, ZIn, eps, 7, 1e-14); + integral.res = -integral.res; + } + return integral.res; +} + +// Invert z(Z) via root-finding. +double +root_Z_psiz(double Z, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + return app->z_in - z_psiZ(app->psi_in, Z, ctx); +} + +double +Z_psiz(double psiIn, double zIn, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double maxL = app->Z_max - app->Z_min; + double eps = maxL / app->Nz; // Interestingly using a smaller eps yields larger errors in some geo quantities. + app->psi_in = psiIn; + app->z_in = zIn; + struct gkyl_qr_res Zout; + if (zIn >= 0.0) { + double fl = root_Z_psiz(-eps, ctx); + double fr = root_Z_psiz(app->Z_max + eps, ctx); + Zout = gkyl_ridders(root_Z_psiz, ctx, -eps, app->Z_max + eps, fl, fr, 1000, 1e-14); + } + else { + double fl = root_Z_psiz(app->Z_min - eps, ctx); + double fr = root_Z_psiz(eps, ctx); + Zout = gkyl_ridders(root_Z_psiz, ctx, app->Z_min - eps, eps, fl, fr, 1000, 1e-14); + } + return Zout.res; +} + +void +eval_density_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->NSrcIon; +} + +void +eval_upar_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + fout[0] = 0.0; +} + +void +eval_temp_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->TSrc0Ion; +} + +// Ion initial conditions +void +eval_density_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->n0; +} + +void +eval_upar_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + fout[0] = 0.0; +} + +void +eval_temp_par_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->Ti_par0; +} + +void +eval_temp_perp_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->Ti_perp0; +} + +void +evalNuIon(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->nuIon; +} + +// Geometry evaluation functions for the gk app +// mapc2p must assume a 3d input xc +void +mapc2p(double t, const double *xc, double *GKYL_RESTRICT xp, void *ctx) +{ + double psi = xc[0]; + double theta = xc[1]; + double z = xc[2]; + + double Z = Z_psiz(psi, z, ctx); + double R = R_psiZ(psi, Z, ctx); + + // Cartesian coordinates on plane perpendicular to Z axis. + double x = R * cos(theta); + double y = R * sin(theta); + xp[0] = x; + xp[1] = y; + xp[2] = Z; +} + +// bfield_func must assume a 3d input xc +void +bfield_func(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) +{ + double z = xc[2]; + + struct gk_mirror_ctx *app = ctx; + double psi = psi_RZ(app->RatZeq0, 0.0, ctx); // Magnetic flux function psi of field line. + double Z = Z_psiz(psi, z, ctx); + double BRad, BZ, Bmag; + Bfield_psiZ(psi, Z, ctx, &BRad, &BZ, &Bmag); + + double phi = xc[1]; + // zc are computational coords. + // Set Cartesian components of magnetic field. + fout[0] = BRad * cos(phi); + fout[1] = BRad * sin(phi); + fout[2] = BZ; +} + +void mapc2p_vel_ion(double t, const double *vc, double *GKYL_RESTRICT vp, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double vpar_max_ion = app->vpar_max_ion; + double mu_max_ion = app->mu_max_ion; + + double cvpar = vc[0], cmu = vc[1]; + double b = 1.4; + vp[0] = vpar_max_ion * tan(cvpar * b) / tan(b); + // Cubic map in mu. + vp[1] = mu_max_ion * pow(cmu, 3); +} + +struct gk_mirror_ctx +create_ctx(void) +{ + int cdim = 1, vdim = 2; // Dimensionality. + + // Universal constant parameters. + double eps0 = GKYL_EPSILON0; + double mu0 = GKYL_MU0; + double eV = GKYL_ELEMENTARY_CHARGE; + double mp = GKYL_PROTON_MASS; + double me = GKYL_ELECTRON_MASS; + double qi = eV; // ion charge + double qe = -eV; // electron charge + + // Plasma parameters. + double mi = 2.014 * mp; + double Te0 = 940 * eV; + double n0 = 3e19; + double B_p = 0.53; + double beta = 0.4; + double tau = pow(B_p, 2.) * beta / (2.0 * mu0 * n0 * Te0) - 1.; + double Ti0 = tau * Te0; + + double nuFrac = 1.0; + // Ion-ion collision freq. + double logLambdaIon = 6.6 - 0.5 * log(n0 / 1e20) + 1.5 * log(Ti0 / eV); + double nuIon = nuFrac * logLambdaIon * pow(eV, 4.) * n0 / + (12 * pow(M_PI, 3. / 2.) * pow(eps0, 2.) * sqrt(mi) * pow(Ti0, 3. / 2.)); + + // Thermal speeds. + double vti = sqrt(Ti0 / mi); + double vte = sqrt(Te0 / me); + double c_s = sqrt(Te0 / mi); + + // Gyrofrequencies and gyroradii. + double omega_ci = eV * B_p / mi; + double rho_s = c_s / omega_ci; + + // Geometry parameters. + double RatZeq0 = 0.10; // Radius of the field line at Z=0. + // Axial coordinate Z extents. Endure that Z=0 is not on + // the boundary of a cell (due to AD errors). + double Z_min = -3.0; + double Z_max = 3.0; + + // Parameters controlling the magnetic equilibrium model. + double mcB = 1; + double gamma = 0.124904; + double Z_m = 1.0; + + // Source parameters + double NSrcIon = 3.1715e23 / 8.0 / 40.0 / 2.0 * 1.25; + double TSrc0Ion = Ti0 * 1.25; + + // Grid parameters + double vpar_max_ion = 16 * vti; + double vpar_min_ion = -vpar_max_ion; + double mu_max_ion = mi * pow(3. * vti, 2.) / (2. * B_p); + + // Computational velocity space limits. + double vpar_min_ion_c = -1.0; + double vpar_max_ion_c = 1.0; + double mu_min_ion_c = 0.; + double mu_max_ion_c = 1.; + + // Grid DOF: + int Nz = 200; // Number of cells in z direction. + int Nvpar = 48; // Number of cells in parallel velocity direction. + int Nmu = 16; // Number of cells in mu direction. + int poly_order = 1; + + // Initial conditions parameter.s + double Ti_perp0 = 10000 * eV; + double Ti_par0 = 7500 * eV; + + // Factor multiplying collisionless terms. + double alpha_oap = 0.01; + double alpha_fdp = 1.0; + // Duration of each phase. + double tau_oap = 5e-7; + double tau_fdp = 3e-9; + double tau_fdp_extra = 2 * tau_fdp; + int num_cycles = 2; // Number of OAP+FDP cycles to run. + + // Frame counts for each phase type (specified independently) + int num_frames_oap = 1; // Frames per OAP phase + int num_frames_fdp = 1; // Frames per FDP phase + int num_frames_fdp_extra = 2 * num_frames_fdp; // Frames for the extra FDP phase + + // Whether to evolve the field. + bool is_static_field_oap = true; + bool is_static_field_fdp = false; + // Whether to enable positivity. + bool is_positivity_enabled_oap = false; + bool is_positivity_enabled_fdp = true; + // Type of df/dt multipler. + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_oap = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE; + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_fdp = GKYL_GK_FDOT_MULTIPLIER_NONE; + + // Calculate phase structure + double t_end = (tau_oap + tau_fdp) * num_cycles + tau_fdp_extra; + double tau_pair = tau_oap + tau_fdp; // Duration of an OAP+FDP pair. + int num_phases = 2 * num_cycles + 1; + int num_frames = num_cycles * (num_frames_oap + num_frames_fdp) + num_frames_fdp_extra; + + struct gk_poa_phase_params *poa_phases = gkyl_calloc(num_phases, + sizeof(struct gk_poa_phase_params)); + for (int i = 0; i < (num_phases - 1) / 2; i++) { + // OAPs. + poa_phases[2 * i].phase = GK_POA_OAP; + poa_phases[2 * i].num_frames = num_frames_oap; + poa_phases[2 * i].duration = tau_oap; + poa_phases[2 * i].alpha = alpha_oap; + poa_phases[2 * i].is_static_field = is_static_field_oap; + poa_phases[2 * i].fdot_mult_type = fdot_mult_type_oap; + poa_phases[2 * i].is_positivity_enabled = is_positivity_enabled_oap; + + // FDPs. + poa_phases[2 * i + 1].phase = GK_POA_FDP; + poa_phases[2 * i + 1].num_frames = num_frames_fdp; + poa_phases[2 * i + 1].duration = tau_fdp; + poa_phases[2 * i + 1].alpha = alpha_fdp; + poa_phases[2 * i + 1].is_static_field = is_static_field_fdp; + poa_phases[2 * i + 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[2 * i + 1].is_positivity_enabled = is_positivity_enabled_fdp; + } + // Add an extra, longer FDP. + poa_phases[num_phases - 1].phase = GK_POA_FDP; + poa_phases[num_phases - 1].num_frames = num_frames_fdp_extra; + poa_phases[num_phases - 1].duration = tau_fdp_extra; + poa_phases[num_phases - 1].alpha = alpha_fdp; + poa_phases[num_phases - 1].is_static_field = is_static_field_fdp; + poa_phases[num_phases - 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[num_phases - 1].is_positivity_enabled = is_positivity_enabled_fdp; + + double write_phase_freq = 0.5; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). + double int_diag_calc_freq = 5; // Frequency of calculating integrated diagnostics (as a factor of num_frames). + double dt_failure_tol = 1.0e-4; // Minimum allowable fraction of initial time-step. + int num_failures_max = 20; // Maximum allowable number of consecutive small time-steps. + + struct gk_mirror_ctx ctx = { + .cdim = cdim, .vdim = vdim, + .mi = mi, .qi = qi, + .me = me, .qe = qe, + .Te0 = Te0, .Ti0 = Ti0, .n0 = n0, + .B_p = B_p, .beta = beta, .tau = tau, + .nuFrac = nuFrac, .logLambdaIon = logLambdaIon, .nuIon = nuIon, + .vti = vti, .vte = vte, .c_s = c_s, + .omega_ci = omega_ci, .rho_s = rho_s, + .RatZeq0 = RatZeq0, + .Z_min = Z_min, .Z_max = Z_max, + // Parameters controlling the magnetic equilibrium model. + .mcB = mcB, .gamma = gamma, + .Z_m = Z_m, + // Initial condition parameters. + .Ti_perp0 = Ti_perp0, .Ti_par0 = Ti_par0, + // Source parameters + .NSrcIon = NSrcIon, + .TSrc0Ion = TSrc0Ion, + // Physical velocity space limits. + .vpar_min_ion = vpar_min_ion, + .vpar_max_ion = vpar_max_ion, + .mu_max_ion = mu_max_ion, + // Computational velocity space limits. + .vpar_min_ion_c = vpar_min_ion_c, + .vpar_max_ion_c = vpar_max_ion_c, + .mu_min_ion_c = mu_min_ion_c, + .mu_max_ion_c = mu_max_ion_c, + // Grid DOF. + .Nz = Nz, + .Nvpar = Nvpar, + .Nmu = Nmu, + .cells = { Nz, Nvpar, Nmu }, + .poly_order = poly_order, + // Time integration and I/O parameters. + .t_end = t_end, + .num_frames = num_frames, + .num_phases = num_phases, + .poa_phases = poa_phases, + .write_phase_freq = write_phase_freq, + .int_diag_calc_freq = int_diag_calc_freq, + .dt_failure_tol = dt_failure_tol, + .num_failures_max = num_failures_max, + }; + + // Populate a couple more values in the context. + ctx.psi_eval = psi_RZ(ctx.RatZeq0, 0., &ctx); + ctx.z_min = z_psiZ(ctx.psi_eval, ctx.Z_min, &ctx); + ctx.z_max = z_psiZ(ctx.psi_eval, ctx.Z_max, &ctx); + + return ctx; +} + +void +release_ctx(struct gk_mirror_ctx *ctx) +{ + gkyl_free(ctx->poa_phases); +} + +void +calc_integrated_diagnostics(struct gkyl_tm_trigger *iot, gkyl_gyrokinetic_app *app, + double t_curr, bool force_calc, double dt) +{ + if (gkyl_tm_trigger_check_and_bump(iot, t_curr) || force_calc) { + gkyl_gyrokinetic_app_calc_field_energy(app, t_curr); + gkyl_gyrokinetic_app_calc_integrated_mom(app, t_curr); + + if (!(dt < 0.0) ) + gkyl_gyrokinetic_app_save_dt(app, t_curr, dt); + } +} + +void +write_data(struct gkyl_tm_trigger *iot_conf, struct gkyl_tm_trigger *iot_phase, + gkyl_gyrokinetic_app *app, double t_curr, bool force_write) +{ + bool trig_now_conf = gkyl_tm_trigger_check_and_bump(iot_conf, t_curr); + if (trig_now_conf || force_write) { + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; + gkyl_gyrokinetic_app_write_conf(app, t_curr, frame); + + gkyl_gyrokinetic_app_write_field_energy(app); + gkyl_gyrokinetic_app_write_integrated_mom(app); + gkyl_gyrokinetic_app_write_dt(app); + } + + bool trig_now_phase = gkyl_tm_trigger_check_and_bump(iot_phase, t_curr); + if (trig_now_phase || force_write) { + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; + + gkyl_gyrokinetic_app_write_phase(app, t_curr, frame); + } +} + +struct time_frame_state { + double t_curr; // Current simulation time. + double t_end; // End time of current phase. + int frame_curr; // Current frame. + int num_frames; // Number of frames at the end of current phase. +}; + +void reset_io_triggers(struct gk_mirror_ctx *ctx, struct time_frame_state *tfs, + struct gkyl_tm_trigger *trig_write_conf, struct gkyl_tm_trigger *trig_write_phase, + struct gkyl_tm_trigger *trig_calc_intdiag) +{ + // Reset I/O triggers: + double t_curr = tfs->t_curr; + double t_end = tfs->t_end; + int frame_curr = tfs->frame_curr; + int num_frames = tfs->num_frames; + int num_int_diag_calc = ctx->int_diag_calc_freq * num_frames; + + // Prevent division by zero when frame_curr equals num_frames + int frames_remaining = num_frames - frame_curr; + double time_remaining = t_end - t_curr; + + trig_write_conf->dt = time_remaining / frames_remaining; + trig_write_conf->tcurr = t_curr; + trig_write_conf->curr = frame_curr; + + trig_write_phase->dt = time_remaining / (ctx->write_phase_freq * frames_remaining); + trig_write_phase->tcurr = t_curr; + trig_write_phase->curr = frame_curr; + + int diag_frames = GKYL_MAX2(frames_remaining, + (num_int_diag_calc / num_frames) * frames_remaining); + trig_calc_intdiag->dt = time_remaining / diag_frames; + trig_calc_intdiag->tcurr = t_curr; + trig_calc_intdiag->curr = frame_curr; +} + +void run_phase(gkyl_gyrokinetic_app *app, struct gk_mirror_ctx *ctx, double num_steps, + struct gkyl_tm_trigger *trig_write_conf, struct gkyl_tm_trigger *trig_write_phase, + struct gkyl_tm_trigger *trig_calc_intdiag, struct time_frame_state *tfs, + struct gk_poa_phase_params *pparams) +{ + tfs->t_end = tfs->t_curr + pparams->duration; + tfs->num_frames = tfs->frame_curr + pparams->num_frames; + + // Run an OAP or FDP. + double t_curr = tfs->t_curr; + double t_end = tfs->t_end; + + // Reset I/O triggers: + reset_io_triggers(ctx, tfs, trig_write_conf, trig_write_phase, trig_calc_intdiag); + + // Reset simulation parameters and function pointers. + struct gkyl_gyrokinetic_collisionless collisionless_inp = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = pparams->alpha, + }; + struct gkyl_gyrokinetic_fdot_multiplier fdot_mult_inp = { + .num_multipliers = 1, + .multiplier[0] = { + .type = pparams->fdot_mult_type, + .cellwise_const = true, + .write_diagnostics = true, + }, + }; + struct gkyl_gyrokinetic_field field_inp = { + .gkfield_id = GKYL_GK_FIELD_BOLTZMANN, + .electron_mass = ctx->me, + .electron_charge = ctx->qe, + .electron_temp = ctx->Te0, + .polarization_bmag = ctx->B_p, + .is_static = pparams->is_static_field, + }; + struct gkyl_gyrokinetic_positivity positivity_inp = { + .type = pparams->is_positivity_enabled? GKYL_GK_POSITIVITY_SHIFT : GKYL_GK_POSITIVITY_NONE, + .write_diagnostics = pparams->is_positivity_enabled, + }; + + gkyl_gyrokinetic_app_reset_species_collisionless(app, t_curr, "ion", collisionless_inp); + gkyl_gyrokinetic_app_reset_species_fdot_multiplier(app, t_curr, "ion", fdot_mult_inp); + gkyl_gyrokinetic_app_reset_species_positivity(app, t_curr, "ion", positivity_inp); + gkyl_gyrokinetic_app_reset_field(app, t_curr, field_inp); + + // Compute initial guess of maximum stable time-step. + double dt = t_end - t_curr; + + // Initialize small time-step check. + double dt_init = -1.0, dt_failure_tol = ctx->dt_failure_tol; + int num_failures = 0, num_failures_max = ctx->num_failures_max; + + long step = 1; + while ((t_curr < t_end) && (step <= num_steps)) { + if (step == 1 || step % 1 == 0) + gkyl_gyrokinetic_app_cout(app, stdout, "Taking time-step at t = %g ...", t_curr); + + dt = fmin(dt, t_end - t_curr); // Don't step beyond t_end. + struct gkyl_update_status status = gkyl_gyrokinetic_update(app, dt); + + if (step == 1 || step % 1 == 0) + gkyl_gyrokinetic_app_cout(app, stdout, " dt = %g\n", status.dt_actual); + + if (!status.success) { + gkyl_gyrokinetic_app_cout(app, stdout, "** Update method failed! Aborting simulation ....\n"); + break; + } + t_curr += status.dt_actual; + dt = status.dt_suggested; + + calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, t_curr > t_end, status.dt_actual); + write_data(trig_write_conf, trig_write_phase, app, t_curr, t_curr > t_end); + + if (dt_init < 0.0) { + dt_init = status.dt_actual; + } + else if (status.dt_actual < dt_failure_tol * dt_init) { + num_failures += 1; + + gkyl_gyrokinetic_app_cout(app, stdout, "WARNING: Time-step dt = %g", status.dt_actual); + gkyl_gyrokinetic_app_cout(app, stdout, " is below %g*dt_init ...", dt_failure_tol); + gkyl_gyrokinetic_app_cout(app, stdout, " num_failures = %d\n", num_failures); + if (num_failures >= num_failures_max) { + gkyl_gyrokinetic_app_cout(app, stdout, "ERROR: Time-step was below %g*dt_init ", + dt_failure_tol); + gkyl_gyrokinetic_app_cout(app, stdout, "%d consecutive times. Aborting simulation ....\n", + num_failures_max); + calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, true, status.dt_actual); + write_data(trig_write_conf, trig_write_phase, app, t_curr, true); + break; + } + } + else { + num_failures = 0; + } + + step += 1; + } + + tfs->t_curr = t_curr; + tfs->frame_curr = tfs->frame_curr + pparams->num_frames; +} + +int main(int argc, char **argv) +{ + struct gkyl_app_args app_args = parse_app_args(argc, argv); + +#ifdef GKYL_HAVE_MPI + if (app_args.use_mpi) MPI_Init(&argc, &argv); +#endif + + if (app_args.trace_mem) { + gkyl_cu_dev_mem_debug_set(true); + gkyl_mem_debug_set(true); + } + + struct gk_mirror_ctx ctx = create_ctx(); // Context for init functions. + + int cells_x[ctx.cdim], cells_v[ctx.vdim]; + for (int d = 0; d < ctx.cdim; d++) { + cells_x[d] = APP_ARGS_CHOOSE(app_args.xcells[d], ctx.cells[d]); + } + for (int d = 0; d < ctx.vdim; d++) { + cells_v[d] = APP_ARGS_CHOOSE(app_args.vcells[d], ctx.cells[ctx.cdim + d]); + } + + // Construct communicator for use in app. + struct gkyl_comm *comm = gkyl_gyrokinetic_comms_new(app_args.use_mpi, app_args.use_gpu, stderr); + + struct gkyl_gyrokinetic_species ion = { + .name = "ion", + .charge = ctx.qi, .mass = ctx.mi, + .vdim = ctx.vdim, + .lower = { ctx.vpar_min_ion_c, ctx.mu_min_ion_c }, + .upper = { ctx.vpar_max_ion_c, ctx.mu_max_ion_c }, + .cells = { cells_v[0], cells_v[1] }, + + .polarization_density = ctx.n0, + + .mapc2p = { + .mapping = mapc2p_vel_ion, + .ctx = &ctx, + }, + + .projection = { + .proj_id = GKYL_PROJ_BIMAXWELLIAN, + .density = eval_density_ion, + .upar = eval_upar_ion, + .temppar = eval_temp_par_ion, + .tempperp = eval_temp_perp_ion, + .ctx_density = &ctx, + .ctx_upar = &ctx, + .ctx_temppar = &ctx, + .ctx_tempperp = &ctx, + }, + + .collisionless = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = 1.0, // Will be replaced below. + }, + + .collisions = { + .collision_id = GKYL_LBO_COLLISIONS, + .self_nu = evalNuIon, + .self_nu_ctx = &ctx, + }, + + .source = { + .source_id = GKYL_PROJ_SOURCE, + .num_sources = 1, + .projection[0] = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .density = eval_density_ion_source, + .upar = eval_upar_ion_source, + .temp = eval_temp_ion_source, + .ctx_density = &ctx, + .ctx_upar = &ctx, + .ctx_temp = &ctx, + }, + }, + + .time_rate_multiplier = { + .num_multipliers = 1, + .multiplier[0] = { + .type = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE, + .cellwise_const = true, + .write_diagnostics = true, + }, + }, + .positivity = { + .type = GKYL_GK_POSITIVITY_SHIFT, + .write_diagnostics = true, + }, + + .bcs = { + { .dir = 0, .edge = GKYL_LOWER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + { .dir = 0, .edge = GKYL_UPPER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + }, + + .num_diag_moments = 4, + .diag_moments = { GKYL_F_MOMENT_M1, GKYL_F_MOMENT_M2PAR, GKYL_F_MOMENT_M2PERP, + GKYL_F_MOMENT_BIMAXWELLIAN }, + }; + + struct gkyl_gyrokinetic_field field = { + .gkfield_id = GKYL_GK_FIELD_BOLTZMANN, + .electron_mass = ctx.me, + .electron_charge = ctx.qe, + .electron_temp = ctx.Te0, + .is_static = false, // So solvers are allocated. + }; + + // GK app + struct gkyl_gk app_inp = { + .name = "gk_mirror_tandem_boltz_elc_poa_1x2v", + .cdim = ctx.cdim, + .lower = { ctx.z_min }, + .upper = { ctx.z_max }, + .cells = { cells_x[0] }, + .poly_order = ctx.poly_order, + .basis_type = app_args.basis_type, + + .geometry = { + .geometry_id = GKYL_GEOMETRY_MAPC2P, + .world = { ctx.psi_eval, 0.0 }, + .mapc2p = mapc2p, // Mapping of computational to physical space. + .c2p_ctx = &ctx, + .bfield_func = bfield_func, // Magnetic field. + .bfield_ctx = &ctx, + .position_map_info = { + .id = GKYL_PMAP_CONSTANT_DB_NUMERIC, + .map_strength = 0.5, + .maximum_slope_at_min_B = 2, + .gaussian_std = 0.1, + .gaussian_max_integration_width = 0.25, + }, + }, + + .num_periodic_dir = 0, + .periodic_dirs = {}, + + .num_species = 1, + .species = { ion }, + + .field = field, + + .parallelism = { + .use_gpu = app_args.use_gpu, + .cuts = { app_args.cuts[0] }, + .comm = comm, + }, + }; + + // Set app output name from the executable name (argv[0]). + snprintf(app_inp.name, sizeof(app_inp.name), "%s", app_args.app_name); + + // Create app object. + gkyl_gyrokinetic_app *app = gkyl_gyrokinetic_app_new(&app_inp); + + // Triggers for IO. + struct gkyl_tm_trigger trig_write_conf, trig_write_phase, trig_calc_intdiag; + + struct time_frame_state tfs = { + .t_curr = 0.0, // Initial simulation time. + .frame_curr = 0, // Initial frame. + .t_end = ctx.poa_phases[0].duration, // Final time of 1st phase. + .num_frames = ctx.poa_phases[0].num_frames, // Number of frames in 1st phase. + }; + + int phase_idx_init = 0, phase_idx_end = ctx.num_phases; // Initial and final phase index. + if (app_args.is_restart) { + struct gkyl_app_restart_status status = gkyl_gyrokinetic_app_read_from_frame(app, + app_args.restart_frame); + + if (status.io_status != GKYL_ARRAY_RIO_SUCCESS) { + gkyl_gyrokinetic_app_cout(app, stderr, "*** Failed to read restart file! (%s)\n", + gkyl_array_rio_status_msg(status.io_status)); + goto freeresources; + } + + tfs.frame_curr = status.frame; + tfs.t_curr = status.stime; + + // Find out what phase we are in. + double time_count = 0.0; + int frame_count = 0; + int pit_curr = 0; + for (int pit = 0; pit < ctx.num_phases; pit++) { + time_count += ctx.poa_phases[pit].duration; + frame_count += ctx.poa_phases[pit].num_frames; + if ((tfs.t_curr <= time_count) && (tfs.frame_curr <= frame_count)) { + pit_curr = pit; + break; + } + } + ; + phase_idx_init = pit_curr; + + // Change the duration and number frames so this phase reaches the expected + // time and number of frames and not beyond. + struct gk_poa_phase_params *pparams = &ctx.poa_phases[phase_idx_init]; + pparams->num_frames = frame_count - tfs.frame_curr; + pparams->duration = time_count - tfs.t_curr; + + gkyl_gyrokinetic_app_cout(app, stdout, "Restarting from frame %d", tfs.frame_curr); + gkyl_gyrokinetic_app_cout(app, stdout, " at time = %g\n", tfs.t_curr); + } + else { + gkyl_gyrokinetic_app_apply_ic(app, tfs.t_curr); + + // Write out ICs. + reset_io_triggers(&ctx, &tfs, &trig_write_conf, &trig_write_phase, &trig_calc_intdiag); + + calc_integrated_diagnostics(&trig_calc_intdiag, app, tfs.t_curr, true, -1.0); + write_data(&trig_write_conf, &trig_write_phase, app, tfs.t_curr, true); + } + + if (app_args.num_steps != INT_MAX) + phase_idx_end = 1; + + // Loop over number of number of phases; + for (int pit = phase_idx_init; pit < phase_idx_end; pit++) { + gkyl_gyrokinetic_app_cout(app, stdout, "\nRunning phase %d @ t = %.9e ... \n", pit, tfs.t_curr); + struct gk_poa_phase_params *phase_params = &ctx.poa_phases[pit]; + run_phase(app, &ctx, app_args.num_steps, &trig_write_conf, &trig_write_phase, + &trig_calc_intdiag, &tfs, phase_params); + } + + gkyl_gyrokinetic_app_stat_write(app); + + struct gkyl_gyrokinetic_stat stat = gkyl_gyrokinetic_app_stat(app); // fetch simulation statistics + gkyl_gyrokinetic_app_cout(app, stdout, "\n"); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of update calls %ld\n", stat.nup); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of forward-Euler calls %ld\n", stat.nfeuler); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of RK stage-2 failures %ld\n", stat.nstage_2_fail); + if (stat.nstage_2_fail > 0) { + gkyl_gyrokinetic_app_cout(app, stdout, "Max rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[1]); + gkyl_gyrokinetic_app_cout(app, stdout, "Min rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[0]); + } + gkyl_gyrokinetic_app_cout(app, stdout, "Number of RK stage-3 failures %ld\n", stat.nstage_3_fail); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of write calls %ld\n", stat.n_io); + gkyl_gyrokinetic_app_print_timings(app, stdout); + +freeresources: + // simulation complete, free app + gkyl_gyrokinetic_app_release(app); + gkyl_gyrokinetic_comms_release(comm); + release_ctx(&ctx); + +#ifdef GKYL_HAVE_MPI + if (app_args.use_mpi) + MPI_Finalize(); +#endif + return 0; +} \ No newline at end of file diff --git a/gyrokinetic/creg/rt_gk_wham_boltz_elc_poa_1x2v_p1.c b/gyrokinetic/creg/rt_gk_wham_boltz_elc_poa_1x2v_p1.c index cdb60a887f..dcf0e494b9 100644 --- a/gyrokinetic/creg/rt_gk_wham_boltz_elc_poa_1x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_wham_boltz_elc_poa_1x2v_p1.c @@ -273,7 +273,7 @@ create_ctx(void) int num_phases = 2*num_cycles + 1; int num_frames = num_cycles * (num_frames_oap + num_frames_fdp) + num_frames_fdp_extra; - struct gk_poa_phase_params *poa_phases = gkyl_malloc(num_phases * sizeof(struct gk_poa_phase_params)); + struct gk_poa_phase_params *poa_phases = gkyl_calloc(num_phases, sizeof(struct gk_poa_phase_params)); for (int i=0; i<(num_phases-1)/2; i++) { // OAPs. poa_phases[2*i].phase = GK_POA_OAP; diff --git a/gyrokinetic/creg/rt_gk_wham_kinetic_poa_1x2v_p1.c b/gyrokinetic/creg/rt_gk_wham_kinetic_poa_1x2v_p1.c new file mode 100644 index 0000000000..50c3ee0932 --- /dev/null +++ b/gyrokinetic/creg/rt_gk_wham_kinetic_poa_1x2v_p1.c @@ -0,0 +1,976 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +// State of the pseudo orbit-averaged integrator. +enum gk_poa_state { + GK_POA_NONE = 0, // Haven't started. + GK_POA_OAP, // Orbit averaged phase. + GK_POA_FDP, // Full dynamics phase. + GK_POA_COMPLETED, // Finished simulation. +}; + +struct gk_poa_phase_params { + enum gk_poa_state phase; // Type of phase. + int num_frames; // Number of frames. + double duration; // Duration. + double alpha; // Factor multiplying collisionless terms. + bool is_static_field; // Whether to evolve the field. + bool is_positivity_enabled; // Whether positivity is enabled. + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type; // Type of df/dt multipler. +}; + +// Define the context of the simulation. This is basically all the globals +struct gk_mirror_ctx { + int cdim, vdim; // Dimensionality. + // Plasma parameters + double mi; + double qi; + double me; + double qe; + double Te0; + double n0; + double B_p; + double beta; + double tau; + double Ti0; + double kperpRhos; + // Parameters controlling initial conditions. + double alim; + double nuFrac; + // Electron-electron collision freq. + double logLambdaElc; + double nuElc; + double elc_nuFrac; + // Ion-ion collision freq. + double logLambdaIon; + double nuIon; + // Thermal speeds. + double vti; + double vte; + double c_s; + // Gyrofrequencies and gyroradii. + double omega_ci; + double rho_s; + double kperp; // Perpendicular wavenumber in SI units. + double RatZeq0; // Radius of the field line at Z=0. + // Axial coordinate Z extents. Endure that Z=0 is not on + double z_min; + double z_max; + double psi_min; + double psi_eval; + double psi_max; + // Physics parameters at mirror throat + double vpar_max_ion; + double vpar_max_elc; + double mu_max_ion; + double mu_max_elc; + int Nz; + int Nvpar; + int Nmu; + int cells[GKYL_MAX_DIM]; // Number of cells in all directions. + int poly_order; + + double t_end; // End time. + int num_frames; // Number of output frames. + int num_phases; // Number of phases. + struct gk_poa_phase_params *poa_phases; // Phases to run. + double write_phase_freq; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). + double int_diag_calc_freq; // Frequency of calculating integrated diagnostics (as a factor of num_frames). + double dt_failure_tol; // Minimum allowable fraction of initial time-step. + int num_failures_max; // Maximum allowable number of consecutive small time-steps. + + // Source parameters + double source_amplitude; + double source_sigma; + double ion_source_temp; + double elc_source_temp; +}; + +void +eval_density(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = 1e17; +} + +void +eval_upar(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = 0.0; +} + +void +eval_temp_ion(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->Ti0; +} + +void +eval_temp_elc(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + fout[0] = app->Te0; +} + +void +eval_density_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xn[0]; + double src_amp = app->source_amplitude; + double z_src = 0.0; + double src_sigma = app->source_sigma; + double src_amp_floor = src_amp * 1e-2; + if (fabs(z) <= 1.0) { + fout[0] = src_amp * (1 - pow(fabs(z), 6)); + } + else { + fout[0] = 1e-16; + } +} + +void +eval_upar_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + fout[0] = 0.0; +} + +void +eval_temp_ion_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xn[0]; + double TSrc0 = app->ion_source_temp; + double Tfloor = TSrc0 * 1e-2; + if (fabs(z) <= 1.0) { + fout[0] = TSrc0; + } + else { + fout[0] = Tfloor; + } +} + +void +eval_temp_elc_source(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, + void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xn[0]; + double TSrc0 = app->elc_source_temp; // Using same temp as ion source for simplicity + double Tfloor = TSrc0 * 1e-2; + if (fabs(z) <= 1.0) { + fout[0] = TSrc0; + } + else { + fout[0] = Tfloor; + } +} + +// Potential initial condition +void +eval_potential(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double z = xn[0]; + double z_m = 0.98; + double z_max = app->z_max; + double sigma = 0.2 * z_m; + double center_potential = 8.0 * app->Te0 / app->qi; + if (fabs(z) <= sigma) { + fout[0] = center_potential; + } + else { + fout[0] = center_potential * (1 - (fabs(z) - sigma) / (z_max - sigma)); + } +} + +void mapc2p_vel_ion(double t, const double *vc, double *GKYL_RESTRICT vp, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double vpar_max_ion = app->vpar_max_ion; + double mu_max_ion = app->mu_max_ion; + + double cvpar = vc[0], cmu = vc[1]; + double b = 1.45; + double linear_velocity_threshold = 1. / 6.; + double frac_linear = 1 / b * atan(linear_velocity_threshold * tan(b)); + if (fabs(cvpar) < frac_linear) { + double func_frac = tan(frac_linear * b) / tan(b); + vp[0] = vpar_max_ion * func_frac * cvpar / frac_linear; + } + else { + vp[0] = vpar_max_ion * tan(cvpar * b) / tan(b); + } + // Quadratic map in mu. + vp[1] = mu_max_ion * pow(cmu, 3); +} + +void mapc2p_vel_elc(double t, const double *vc, double *GKYL_RESTRICT vp, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double vpar_max_elc = app->vpar_max_elc; + double mu_max_elc = app->mu_max_elc; + + double cvpar = vc[0], cmu = vc[1]; + double b = 1.45; + double linear_velocity_threshold = 1. / 6.; + double frac_linear = 1 / b * atan(linear_velocity_threshold * tan(b)); + if (fabs(cvpar) < frac_linear) { + double func_frac = tan(frac_linear * b) / tan(b); + vp[0] = vpar_max_elc * func_frac * cvpar / frac_linear; + } + else { + vp[0] = vpar_max_elc * tan(cvpar * b) / tan(b); + } + // Quadratic map in mu. + vp[1] = mu_max_elc * pow(cmu, 3.0 / 2.0); +} + +struct gk_mirror_ctx +create_ctx(void) +{ + int cdim = 1, vdim = 2; // Dimensionality. + + // Universal constant parameters. + double eps0 = GKYL_EPSILON0; + double mu0 = GKYL_MU0; // Not sure if this is right + double eV = GKYL_ELEMENTARY_CHARGE; + double mp = GKYL_PROTON_MASS; // ion mass + double me = GKYL_ELECTRON_MASS; + double qi = eV; // ion charge + double qe = -eV; // electron charge + + // Plasma parameters. + double mi = 2.014 * mp; + double Te0 = 940 * eV; + double n0 = 3e19; + double B_p = 0.53; + double beta = 0.4; + double tau = pow(B_p, 2.) * beta / (2.0 * mu0 * n0 * Te0) - 1.; + double Ti0 = tau * Te0; + double kperpRhos = 0.1; + + // Parameters controlling initial conditions. + double alim = 0.125; + double alphaIC0 = 2; + double alphaIC1 = 10; + + double nuFrac = 1.0; + double elc_nuFrac = 1 / 5.489216862238348; + // Electron-electron collision freq. + double logLambdaElc = 6.6 - 0.5 * log(n0 / 1e20) + 1.5 * log(Te0 / eV); + double nuElc = elc_nuFrac * nuFrac * logLambdaElc * pow(eV, 4.) * n0 / + (6. * sqrt(2.) * pow(M_PI, 3. / 2.) * pow(eps0, 2.) * sqrt(me) * pow(Te0, 3. / 2.)); + // Ion-ion collision freq. + double logLambdaIon = 6.6 - 0.5 * log(n0 / 1e20) + 1.5 * log(Ti0 / eV); + double nuIon = nuFrac * logLambdaIon * pow(eV, 4.) * n0 / + (12 * pow(M_PI, 3. / 2.) * pow(eps0, 2.) * sqrt(mi) * pow(Ti0, 3. / 2.)); + + // Thermal speeds. + double vti = sqrt(Ti0 / mi); + double vte = sqrt(Te0 / me); + double c_s = sqrt(Te0 / mi); + + // Gyrofrequencies and gyroradii. + double omega_ci = eV * B_p / mi; + double rho_s = c_s / omega_ci; + + // Perpendicular wavenumber in SI units: + double kperp = kperpRhos / rho_s; + + // Geometry parameters. + double z_min = -2.0; + double z_max = 2.0; + double psi_eval = 1e-3; + + // Grid parameters + double vpar_max_elc = 30 * vte; + double mu_max_elc = me * pow(3. * vte, 2.) / (2. * B_p); + double vpar_max_ion = 30 * vti; + double mu_max_ion = mi * pow(3. * vti, 2.) / (2. * B_p); + int Nz = 32; + int Nvpar = 32; // Number of cells in the paralell velocity direction 96 + int Nmu = 16; // Number of cells in the mu direction 192 + int poly_order = 1; + + // Factor multiplying collisionless terms. + double alpha_oap = 0.01; + double alpha_fdp = 1.0; + // Duration of each phase. + double tau_oap = 1.5e-8; + double tau_fdp = 1.5e-10; + double tau_fdp_extra = 2 * tau_fdp; + int num_cycles = 2; // Number of OAP+FDP cycles to run. + + // Frame counts for each phase type (specified independently) + int num_frames_oap = 1; // Frames per OAP phase + int num_frames_fdp = 1; // Frames per FDP phase + int num_frames_fdp_extra = 2 * num_frames_fdp; // Frames for the extra FDP phase + + // Whether to evolve the field. + bool is_static_field_oap = true; + bool is_static_field_fdp = false; + // Whether to enable positivity. + bool is_positivity_enabled_oap = false; + bool is_positivity_enabled_fdp = true; + // Type of df/dt multipler. + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_oap = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE; + enum gkyl_gyrokinetic_fdot_multiplier_type fdot_mult_type_fdp = GKYL_GK_FDOT_MULTIPLIER_NONE; + + // Calculate phase structure + double t_end = (tau_oap + tau_fdp) * num_cycles + tau_fdp_extra; + double tau_pair = tau_oap + tau_fdp; // Duration of an OAP+FDP pair. + int num_phases = 2 * num_cycles + 1; + int num_frames = num_cycles * (num_frames_oap + num_frames_fdp) + num_frames_fdp_extra; + + struct gk_poa_phase_params *poa_phases = gkyl_calloc(num_phases, + sizeof(struct gk_poa_phase_params)); + for (int i = 0; i < (num_phases - 1) / 2; i++) { + // OAPs. + poa_phases[2 * i].phase = GK_POA_OAP; + poa_phases[2 * i].num_frames = num_frames_oap; + poa_phases[2 * i].duration = tau_oap; + poa_phases[2 * i].alpha = alpha_oap; + poa_phases[2 * i].is_static_field = is_static_field_oap; + poa_phases[2 * i].fdot_mult_type = fdot_mult_type_oap; + poa_phases[2 * i].is_positivity_enabled = is_positivity_enabled_oap; + + // FDPs. + poa_phases[2 * i + 1].phase = GK_POA_FDP; + poa_phases[2 * i + 1].num_frames = num_frames_fdp; + poa_phases[2 * i + 1].duration = tau_fdp; + poa_phases[2 * i + 1].alpha = alpha_fdp; + poa_phases[2 * i + 1].is_static_field = is_static_field_fdp; + poa_phases[2 * i + 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[2 * i + 1].is_positivity_enabled = is_positivity_enabled_fdp; + } + // Add an extra, longer FDP. + poa_phases[num_phases - 1].phase = GK_POA_FDP; + poa_phases[num_phases - 1].num_frames = num_frames_fdp_extra; + poa_phases[num_phases - 1].duration = tau_fdp_extra; + poa_phases[num_phases - 1].alpha = alpha_fdp; + poa_phases[num_phases - 1].is_static_field = is_static_field_fdp; + poa_phases[num_phases - 1].fdot_mult_type = fdot_mult_type_fdp; + poa_phases[num_phases - 1].is_positivity_enabled = is_positivity_enabled_fdp; + + double write_phase_freq = 0.5; // Frequency of writing phase-space diagnostics (as a fraction of num_frames). + double int_diag_calc_freq = 5; // Frequency of calculating integrated diagnostics (as a factor of num_frames). + double dt_failure_tol = 1.0e-4; // Minimum allowable fraction of initial time-step. + int num_failures_max = 20; // Maximum allowable number of consecutive small time-steps. + + // Source parameters + double source_amplitude = 1.e20; + double source_sigma = 0.5; + double ion_source_temp = 5000. * eV; + double elc_source_temp = 5000. * eV; // Using same temp as ion source for simplicity + + struct gk_mirror_ctx ctx = { + .cdim = cdim, + .vdim = vdim, + .mi = mi, + .qi = qi, + .me = me, + .qe = qe, + .Te0 = Te0, + .n0 = n0, + .B_p = B_p, + .beta = beta, + .tau = tau, + .Ti0 = Ti0, + .kperpRhos = kperpRhos, + .alim = alim, + .nuFrac = nuFrac, + .logLambdaElc = logLambdaElc, + .nuElc = nuElc, + .elc_nuFrac = elc_nuFrac, + .logLambdaIon = logLambdaIon, + .nuIon = nuIon, + .vti = vti, + .vte = vte, + .c_s = c_s, + .omega_ci = omega_ci, + .rho_s = rho_s, + .kperp = kperp, + .z_min = z_min, + .z_max = z_max, + .psi_eval = psi_eval, + .vpar_max_ion = vpar_max_ion, + .vpar_max_elc = vpar_max_elc, + .mu_max_ion = mu_max_ion, + .mu_max_elc = mu_max_elc, + .Nz = Nz, + .Nvpar = Nvpar, + .Nmu = Nmu, + .cells = { Nz, Nvpar, Nmu }, + .poly_order = poly_order, + .t_end = t_end, + .num_frames = num_frames, + .num_phases = num_phases, + .poa_phases = poa_phases, + .write_phase_freq = write_phase_freq, + .int_diag_calc_freq = int_diag_calc_freq, + .dt_failure_tol = dt_failure_tol, + .num_failures_max = num_failures_max, + }; + + return ctx; +} + +void +release_ctx(struct gk_mirror_ctx *ctx) +{ + gkyl_free(ctx->poa_phases); +} + +void +calc_integrated_diagnostics(struct gkyl_tm_trigger *iot, gkyl_gyrokinetic_app *app, + double t_curr, bool force_calc, double dt) +{ + if (gkyl_tm_trigger_check_and_bump(iot, t_curr) || force_calc) { + gkyl_gyrokinetic_app_calc_field_energy(app, t_curr); + gkyl_gyrokinetic_app_calc_integrated_mom(app, t_curr); + + if (!(dt < 0.0) ) + gkyl_gyrokinetic_app_save_dt(app, t_curr, dt); + } +} + +void +write_data(struct gkyl_tm_trigger *iot_conf, struct gkyl_tm_trigger *iot_phase, + gkyl_gyrokinetic_app *app, double t_curr, bool force_write) +{ + bool trig_now_conf = gkyl_tm_trigger_check_and_bump(iot_conf, t_curr); + if (trig_now_conf || force_write) { + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; + gkyl_gyrokinetic_app_write_conf(app, t_curr, frame); + + gkyl_gyrokinetic_app_write_field_energy(app); + gkyl_gyrokinetic_app_write_integrated_mom(app); + gkyl_gyrokinetic_app_write_dt(app); + } + + bool trig_now_phase = gkyl_tm_trigger_check_and_bump(iot_phase, t_curr); + if (trig_now_phase || force_write) { + int frame = (!trig_now_conf) && force_write? iot_conf->curr : iot_conf->curr - 1; + + gkyl_gyrokinetic_app_write_phase(app, t_curr, frame); + } +} + +struct time_frame_state { + double t_curr; // Current simulation time. + double t_end; // End time of current phase. + int frame_curr; // Current frame. + int num_frames; // Number of frames at the end of current phase. +}; + +void reset_io_triggers(struct gk_mirror_ctx *ctx, struct time_frame_state *tfs, + struct gkyl_tm_trigger *trig_write_conf, struct gkyl_tm_trigger *trig_write_phase, + struct gkyl_tm_trigger *trig_calc_intdiag) +{ + // Reset I/O triggers: + double t_curr = tfs->t_curr; + double t_end = tfs->t_end; + int frame_curr = tfs->frame_curr; + int num_frames = tfs->num_frames; + int num_int_diag_calc = ctx->int_diag_calc_freq * num_frames; + + // Prevent division by zero when frame_curr equals num_frames + int frames_remaining = num_frames - frame_curr; + double time_remaining = t_end - t_curr; + + trig_write_conf->dt = time_remaining / frames_remaining; + trig_write_conf->tcurr = t_curr; + trig_write_conf->curr = frame_curr; + + trig_write_phase->dt = time_remaining / (ctx->write_phase_freq * frames_remaining); + trig_write_phase->tcurr = t_curr; + trig_write_phase->curr = frame_curr; + + int diag_frames = GKYL_MAX2(frames_remaining, + (num_int_diag_calc / num_frames) * frames_remaining); + trig_calc_intdiag->dt = time_remaining / diag_frames; + trig_calc_intdiag->tcurr = t_curr; + trig_calc_intdiag->curr = frame_curr; +} + +void run_phase(gkyl_gyrokinetic_app *app, struct gk_mirror_ctx *ctx, double num_steps, + struct gkyl_tm_trigger *trig_write_conf, struct gkyl_tm_trigger *trig_write_phase, + struct gkyl_tm_trigger *trig_calc_intdiag, struct time_frame_state *tfs, + struct gk_poa_phase_params *pparams) +{ + tfs->t_end = tfs->t_curr + pparams->duration; + tfs->num_frames = tfs->frame_curr + pparams->num_frames; + + // Run an OAP or FDP. + double t_curr = tfs->t_curr; + double t_end = tfs->t_end; + + // Reset I/O triggers: + reset_io_triggers(ctx, tfs, trig_write_conf, trig_write_phase, trig_calc_intdiag); + + // Reset simulation parameters and function pointers. + struct gkyl_gyrokinetic_collisionless collisionless_inp = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = pparams->alpha, + }; + struct gkyl_gyrokinetic_fdot_multiplier fdot_mult_inp = { + .num_multipliers = 1, + .multiplier[0] = { + .type = pparams->fdot_mult_type, + .cellwise_const = true, + .write_diagnostics = true, + }, + }; + struct gkyl_gyrokinetic_field field_inp = { + .polarization_bmag = ctx->B_p, + .kperpSq = pow(ctx->kperp, 2.), + .is_static = pparams->is_static_field, + .time_rate_diagnostics = true, + .polarization_potential = eval_potential, + .polarization_potential_ctx = &ctx, + }; + struct gkyl_gyrokinetic_positivity positivity_inp = { + .type = pparams->is_positivity_enabled? GKYL_GK_POSITIVITY_SHIFT : GKYL_GK_POSITIVITY_NONE, + .write_diagnostics = pparams->is_positivity_enabled, + }; + + gkyl_gyrokinetic_app_reset_species_collisionless(app, t_curr, "ion", collisionless_inp); + gkyl_gyrokinetic_app_reset_species_collisionless(app, t_curr, "elc", collisionless_inp); + gkyl_gyrokinetic_app_reset_species_fdot_multiplier(app, t_curr, "ion", fdot_mult_inp); + gkyl_gyrokinetic_app_reset_species_fdot_multiplier(app, t_curr, "elc", fdot_mult_inp); + gkyl_gyrokinetic_app_reset_species_positivity(app, t_curr, "ion", positivity_inp); + gkyl_gyrokinetic_app_reset_species_positivity(app, t_curr, "elc", positivity_inp); + gkyl_gyrokinetic_app_reset_field(app, t_curr, field_inp); + + // Compute initial guess of maximum stable time-step. + double dt = t_end - t_curr; + + // Initialize small time-step check. + double dt_init = -1.0, dt_failure_tol = ctx->dt_failure_tol; + int num_failures = 0, num_failures_max = ctx->num_failures_max; + + long step = 1; + while ((t_curr < t_end) && (step <= num_steps)) { + gkyl_gyrokinetic_app_cout(app, stdout, "Taking time-step %ld at t = %g ...", step, t_curr); + + dt = fmin(dt, t_end - t_curr); // Don't step beyond t_end. + struct gkyl_update_status status = gkyl_gyrokinetic_update(app, dt); + + gkyl_gyrokinetic_app_cout(app, stdout, " dt = %g\n", status.dt_actual); + + if (!status.success) { + gkyl_gyrokinetic_app_cout(app, stdout, "** Update method failed! Aborting simulation ....\n"); + break; + } + t_curr += status.dt_actual; + dt = status.dt_suggested; + + calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, t_curr >= t_end, status.dt_actual); + write_data(trig_write_conf, trig_write_phase, app, t_curr, t_curr >= t_end); + + if (dt_init < 0.0) { + dt_init = status.dt_actual; + } + else if (status.dt_actual < dt_failure_tol * dt_init) { + num_failures += 1; + + gkyl_gyrokinetic_app_cout(app, stdout, "WARNING: Time-step dt = %g", status.dt_actual); + gkyl_gyrokinetic_app_cout(app, stdout, " is below %g*dt_init ...", dt_failure_tol); + gkyl_gyrokinetic_app_cout(app, stdout, " num_failures = %d\n", num_failures); + if (num_failures >= num_failures_max) { + gkyl_gyrokinetic_app_cout(app, stdout, "ERROR: Time-step was below %g*dt_init ", + dt_failure_tol); + gkyl_gyrokinetic_app_cout(app, stdout, "%d consecutive times. Aborting simulation ....\n", + num_failures_max); + calc_integrated_diagnostics(trig_calc_intdiag, app, t_curr, true, status.dt_actual); + write_data(trig_write_conf, trig_write_phase, app, t_curr, true); + break; + } + } + else { + num_failures = 0; + } + step += 1; + } + + tfs->t_curr = t_curr; + tfs->frame_curr = tfs->frame_curr + pparams->num_frames; +} + +int main(int argc, char **argv) +{ + struct gkyl_app_args app_args = parse_app_args(argc, argv); + +#ifdef GKYL_HAVE_MPI + if (app_args.use_mpi) MPI_Init(&argc, &argv); +#endif + + if (app_args.trace_mem) { + gkyl_cu_dev_mem_debug_set(true); + gkyl_mem_debug_set(true); + } + + struct gk_mirror_ctx ctx = create_ctx(); // Context for init functions. + + int cells_x[ctx.cdim], cells_v[ctx.vdim]; + for (int d = 0; d < ctx.cdim; d++) { + cells_x[d] = APP_ARGS_CHOOSE(app_args.xcells[d], ctx.cells[d]); + } + for (int d = 0; d < ctx.vdim; d++) { + cells_v[d] = APP_ARGS_CHOOSE(app_args.vcells[d], ctx.cells[ctx.cdim + d]); + } + + // Construct communicator for use in app. + struct gkyl_comm *comm = gkyl_gyrokinetic_comms_new(app_args.use_mpi, app_args.use_gpu, stderr); + + struct gkyl_gyrokinetic_species elc = { + .name = "elc", + .charge = ctx.qe, + .mass = ctx.me, + .vdim = ctx.vdim, + .lower = { -1.0, 0.0 }, + .upper = { 1.0, 1.0 }, + .cells = { cells_v[0], cells_v[1] }, + + .polarization_density = ctx.n0, + + .mapc2p = { + .mapping = mapc2p_vel_elc, + .ctx = &ctx, + }, + + .projection = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .density = eval_density, + .ctx_density = &ctx, + .upar = eval_upar, + .ctx_upar = &ctx, + .temp = eval_temp_elc, + .ctx_temp = &ctx, + }, + + .collisionless = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = 1.0, // Will be replaced below. + }, + + .collisions = { + .collision_id = GKYL_LBO_COLLISIONS, + .den_ref = ctx.n0, + .temp_ref = ctx.Te0, + .num_cross_collisions = 1, + .collide_with = { "ion" }, + .write_diagnostics = true, + }, + + .source = { + .source_id = GKYL_PROJ_SOURCE, + .num_sources = 1, + .projection[0] = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .ctx_density = &ctx, + .density = eval_density_source, + .ctx_upar = &ctx, + .upar = eval_upar_source, + .ctx_temp = &ctx, + .temp = eval_temp_elc_source, + }, + .diagnostics = { + .num_diag_moments = 5, + .diag_moments = { GKYL_F_MOMENT_M0, GKYL_F_MOMENT_M1, GKYL_F_MOMENT_M2, GKYL_F_MOMENT_M2PAR, + GKYL_F_MOMENT_M2PERP }, + .num_integrated_diag_moments = 1, + .integrated_diag_moments = { GKYL_F_MOMENT_HAMILTONIAN }, + } + }, + + .time_rate_multiplier = { + .num_multipliers = 1, + .multiplier[0] = { + .type = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE, + .cellwise_const = true, + .write_diagnostics = true, + }, + }, + + .positivity = { + .type = GKYL_GK_POSITIVITY_SHIFT, + .write_diagnostics = true, + }, + + .bcs = { + { .dir = 0, .edge = GKYL_LOWER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + { .dir = 0, .edge = GKYL_UPPER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + }, + + .write_omega_cfl = true, + .num_diag_moments = 8, + .diag_moments = { GKYL_F_MOMENT_BIMAXWELLIAN, GKYL_F_MOMENT_M0, GKYL_F_MOMENT_M1, + GKYL_F_MOMENT_M2, GKYL_F_MOMENT_M2PAR, GKYL_F_MOMENT_M2PERP, + GKYL_F_MOMENT_M3PAR, GKYL_F_MOMENT_M3PERP }, + .num_integrated_diag_moments = 1, + .integrated_diag_moments = { GKYL_F_MOMENT_HAMILTONIAN }, + .time_rate_diagnostics = true, + + .boundary_flux_diagnostics = { + .num_integrated_diag_moments = 1, + .integrated_diag_moments = { GKYL_F_MOMENT_HAMILTONIAN }, + }, + }; + + struct gkyl_gyrokinetic_species ion = { + .name = "ion", + .charge = ctx.qi, + .mass = ctx.mi, + .vdim = ctx.vdim, + .lower = { -1.0, 0.0 }, + .upper = { 1.0, 1.0 }, + .cells = { cells_v[0], cells_v[1] }, + .polarization_density = ctx.n0, + .scale_with_polarization = true, + + .projection = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .density = eval_density, + .ctx_density = &ctx, + .upar = eval_upar, + .ctx_upar = &ctx, + .temp = eval_temp_ion, + .ctx_temp = &ctx, + }, + + .mapc2p = { + .mapping = mapc2p_vel_ion, + .ctx = &ctx, + }, + + .collisionless = { + .type = GKYL_GK_COLLISIONLESS_ES, + .scale_factor = 1.0, // Will be replaced below. + }, + + .time_rate_multiplier = { + .num_multipliers = 1, + .multiplier[0] = { + .type = GKYL_GK_FDOT_MULTIPLIER_LOSS_CONE, + .cellwise_const = true, + .write_diagnostics = true, + }, + }, + + .collisions = { + .collision_id = GKYL_LBO_COLLISIONS, + .den_ref = ctx.n0, + .temp_ref = ctx.Ti0, + .num_cross_collisions = 1, + .collide_with = { "elc" }, + .write_diagnostics = true, + }, + .source = { + .source_id = GKYL_PROJ_SOURCE, + .num_sources = 1, + .projection[0] = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .ctx_density = &ctx, + .density = eval_density_source, + .ctx_upar = &ctx, + .upar = eval_upar_source, + .ctx_temp = &ctx, + .temp = eval_temp_ion_source, + }, + .diagnostics = { + .num_diag_moments = 6, + .diag_moments = { GKYL_F_MOMENT_M0, GKYL_F_MOMENT_M1, GKYL_F_MOMENT_M2, GKYL_F_MOMENT_M2PAR, + GKYL_F_MOMENT_M2PERP, GKYL_F_MOMENT_HAMILTONIAN }, + .num_integrated_diag_moments = 1, + .integrated_diag_moments = { GKYL_F_MOMENT_M0M1M2PARM2PERP }, + }, + }, + + .positivity = { + .type = GKYL_GK_POSITIVITY_SHIFT, + .write_diagnostics = true, + }, + + .bcs = { + { .dir = 0, .edge = GKYL_LOWER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + { .dir = 0, .edge = GKYL_UPPER_EDGE, .type = GKYL_BC_GK_SPECIES_SHEATH, }, + }, + .write_omega_cfl = true, + .num_diag_moments = 8, + .diag_moments = { GKYL_F_MOMENT_BIMAXWELLIAN, GKYL_F_MOMENT_M0, GKYL_F_MOMENT_M1, + GKYL_F_MOMENT_M2, GKYL_F_MOMENT_M2PAR, GKYL_F_MOMENT_M2PERP, + GKYL_F_MOMENT_M3PAR, GKYL_F_MOMENT_M3PERP }, + .num_integrated_diag_moments = 1, + .integrated_diag_moments = { GKYL_F_MOMENT_M0M1M2PARM2PERP }, + .time_rate_diagnostics = true, + + .boundary_flux_diagnostics = { + .num_integrated_diag_moments = 1, + .integrated_diag_moments = { GKYL_F_MOMENT_M0M1M2PARM2PERP }, + }, + }; + struct gkyl_gyrokinetic_field field = { + .polarization_bmag = ctx.B_p, + .kperpSq = pow(ctx.kperp, 2.), + .time_rate_diagnostics = true, + .is_static = false, + .polarization_potential = eval_potential, + .polarization_potential_ctx = &ctx, + }; + + struct gkyl_mirror_geo_grid_inp grid_inp = { + .filename_psi = "gyrokinetic/data/unit/wham_hires.geqdsk_psi.gkyl", // psi file to use + .rclose = 0.2, // closest R to region of interest + .zmin = -2.0, // Z of lower boundary + .zmax = 2.0, // Z of upper boundary + .include_axis = false, // Include R=0 axis in grid + .fl_coord = GKYL_GEOMETRY_MIRROR_GRID_GEN_PSI_CART_Z, // coordinate system for psi grid + }; + + struct gkyl_gk app_inp = { // GK app + .name = "gk_wham_kinetic_poa_1x2v_p1", + .cdim = ctx.cdim, + .lower = { ctx.z_min }, + .upper = { ctx.z_max }, + .cells = { cells_x[0] }, + .poly_order = ctx.poly_order, + .basis_type = app_args.basis_type, + .geometry = { + .geometry_id = GKYL_GEOMETRY_MIRROR, + .world = { ctx.psi_eval, 0.0 }, + .mirror_grid_info = grid_inp, + }, + .num_periodic_dir = 0, + .periodic_dirs = {}, + .num_species = 2, + .species = { elc, ion }, + .field = field, + .parallelism = { + .use_gpu = app_args.use_gpu, + .cuts = { app_args.cuts[0] }, + .comm = comm, + }, + }; + + // Set app output name from the executable name (argv[0]). + snprintf(app_inp.name, sizeof(app_inp.name), "%s", app_args.app_name); + + // Create app object. + gkyl_gyrokinetic_app *app = gkyl_gyrokinetic_app_new(&app_inp); + + // Triggers for IO. + struct gkyl_tm_trigger trig_write_conf, trig_write_phase, trig_calc_intdiag; + + struct time_frame_state tfs = { + .t_curr = 0.0, // Initial simulation time. + .frame_curr = 0, // Initial frame. + .t_end = ctx.poa_phases[0].duration, // Final time of 1st phase. + .num_frames = ctx.poa_phases[0].num_frames, // Number of frames in 1st phase. + }; + + int phase_idx_init = 0, phase_idx_end = ctx.num_phases; // Initial and final phase index. + if (app_args.is_restart) { + struct gkyl_app_restart_status status = gkyl_gyrokinetic_app_read_from_frame(app, + app_args.restart_frame); + + if (status.io_status != GKYL_ARRAY_RIO_SUCCESS) { + gkyl_gyrokinetic_app_cout(app, stderr, "*** Failed to read restart file! (%s)\n", + gkyl_array_rio_status_msg(status.io_status)); + goto freeresources; + } + + tfs.frame_curr = status.frame; + tfs.t_curr = status.stime; + + // Find out what phase we are in. + double time_count = 0.0; + int frame_count = 0; + int pit_curr = 0; + for (int pit = 0; pit < ctx.num_phases; pit++) { + time_count += ctx.poa_phases[pit].duration; + frame_count += ctx.poa_phases[pit].num_frames; + if ((tfs.t_curr <= time_count) && (tfs.frame_curr <= frame_count)) { + pit_curr = pit; + break; + } + } + ; + phase_idx_init = pit_curr; + + // Change the duration and number frames so this phase reaches the expected + // time and number of frames and not beyond. + struct gk_poa_phase_params *pparams = &ctx.poa_phases[phase_idx_init]; + pparams->num_frames = frame_count - tfs.frame_curr; + pparams->duration = time_count - tfs.t_curr; + + gkyl_gyrokinetic_app_cout(app, stdout, "Restarting from frame %d", tfs.frame_curr); + gkyl_gyrokinetic_app_cout(app, stdout, " at time = %g\n", tfs.t_curr); + } + else { + gkyl_gyrokinetic_app_apply_ic(app, tfs.t_curr); + + // Write out ICs. + reset_io_triggers(&ctx, &tfs, &trig_write_conf, &trig_write_phase, &trig_calc_intdiag); + + calc_integrated_diagnostics(&trig_calc_intdiag, app, tfs.t_curr, true, -1.0); + write_data(&trig_write_conf, &trig_write_phase, app, tfs.t_curr, true); + } + + if (app_args.num_steps != INT_MAX) + phase_idx_end = 1; + + // Loop over number of number of phases; + for (int pit = phase_idx_init; pit < phase_idx_end; pit++) { + struct gk_poa_phase_params *phase_params = &ctx.poa_phases[pit]; + run_phase(app, &ctx, app_args.num_steps, &trig_write_conf, &trig_write_phase, + &trig_calc_intdiag, &tfs, phase_params); + } + + gkyl_gyrokinetic_app_stat_write(app); + + struct gkyl_gyrokinetic_stat stat = gkyl_gyrokinetic_app_stat(app); // fetch simulation statistics + gkyl_gyrokinetic_app_cout(app, stdout, "\n"); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of update calls %ld\n", stat.nup); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of forward-Euler calls %ld\n", stat.nfeuler); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of RK stage-2 failures %ld\n", stat.nstage_2_fail); + if (stat.nstage_2_fail > 0) { + gkyl_gyrokinetic_app_cout(app, stdout, "Max rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[1]); + gkyl_gyrokinetic_app_cout(app, stdout, "Min rel dt diff for RK stage-2 failures %g\n", + stat.stage_2_dt_diff[0]); + } + gkyl_gyrokinetic_app_cout(app, stdout, "Number of RK stage-3 failures %ld\n", stat.nstage_3_fail); + gkyl_gyrokinetic_app_cout(app, stdout, "Number of write calls %ld\n", stat.n_io); + gkyl_gyrokinetic_app_print_timings(app, stdout); + +freeresources: + // simulation complete, free app + gkyl_gyrokinetic_app_release(app); + gkyl_gyrokinetic_comms_release(comm); + release_ctx(&ctx); + +#ifdef GKYL_HAVE_MPI + if (app_args.use_mpi) + MPI_Finalize(); +#endif + return 0; +} diff --git a/gyrokinetic/creg/rt_gk_wham_nonuniformx_2x2v_p1.c b/gyrokinetic/creg/rt_gk_wham_nonuniformx_2x2v_p1.c index ae0e96a7b0..07c910376a 100644 --- a/gyrokinetic/creg/rt_gk_wham_nonuniformx_2x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_wham_nonuniformx_2x2v_p1.c @@ -812,7 +812,7 @@ int main(int argc, char **argv) .map_strength = 0.5, .maximum_slope_at_min_B = 2, .gaussian_std = 0.2, - .gaussian_max_integration_width = 1.0, + .gaussian_max_integration_width = 0.5, }, }, diff --git a/gyrokinetic/unit/ctest_gk_species_wall_potential.c b/gyrokinetic/unit/ctest_gk_species_wall_potential.c new file mode 100644 index 0000000000..a3ebbc4f7e --- /dev/null +++ b/gyrokinetic/unit/ctest_gk_species_wall_potential.c @@ -0,0 +1,157 @@ +#include + +#include + +#include + +struct wall_profile_ctx { + double offset; + double time_slope; + double space_slope; +}; + +static void +wall_profile(double t, const double *xn, double *fout, void *ctx) +{ + const struct wall_profile_ctx *profile = ctx; + fout[0] = profile->offset + profile->time_slope*t + profile->space_slope*xn[0]; +} + +static gkyl_gyrokinetic_app +make_app(void) +{ + gkyl_gyrokinetic_app app = { + .cdim = 1, + .use_gpu = false, + }; + double lower[] = { -1.0 }, upper[] = { 1.0 }; + int cells[] = { 4 }, ghost[] = { 1 }; + + gkyl_rect_grid_init(&app.grid, app.cdim, lower, upper, cells); + gkyl_cart_modal_serendip(&app.basis, app.cdim, 1); + gkyl_create_grid_ranges(&app.grid, ghost, &app.local_ext, &app.local); + + return app; +} + +static void +check_profile(const gkyl_gyrokinetic_app *app, const struct gkyl_array *phi, + const struct wall_profile_ctx *profile, double tm) +{ + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &app->local_ext); + while (gkyl_range_iter_next(&iter)) { + long lidx = gkyl_range_idx(&app->local_ext, iter.idx); + const double *phi_c = gkyl_array_cfetch(phi, lidx); + double xc[GKYL_MAX_DIM]; + gkyl_rect_grid_cell_center(&app->grid, iter.idx, xc); + + const double eta[] = { -1.0, 0.0, 1.0 }; + for (int i=0; i<3; ++i) { + double x = xc[0] + 0.5*app->grid.dx[0]*eta[i]; + double expected = profile->offset + profile->time_slope*tm + profile->space_slope*x; + TEST_CHECK(fabs(app->basis.eval_expand(&eta[i], phi_c)-expected) < 1e-12); + } + } +} + +static void +test_species_wall_profiles(void) +{ + // A linear profile lies exactly in the P1 basis. Compare its DG expansion + // at both cell endpoints and the center with the closed-form profile, first + // at t=0 and then after advancing each wall independently to t=0.625. + gkyl_gyrokinetic_app app = make_app(); + struct wall_profile_ctx lower_ctx = { + .offset = 1.5, + .time_slope = 2.0, + .space_slope = -0.25, + }; + struct wall_profile_ctx upper_ctx = { + .offset = -0.5, + .time_slope = -1.0, + .space_slope = 0.75, + }; + struct gk_species species = { + .lower_bc[0] = { + .type = GKYL_BC_GK_SPECIES_SHEATH, + .aux_profile = wall_profile, + .aux_ctx = &lower_ctx, + }, + .upper_bc[0] = { + .type = GKYL_BC_GK_SPECIES_SHEATH, + .aux_profile = wall_profile, + .aux_ctx = &upper_ctx, + }, + }; + + gk_species_phi_wall_init(&app, &species.lower_bc[0], &species.phi_wall_lo); + gk_species_phi_wall_init(&app, &species.upper_bc[0], &species.phi_wall_up); + TEST_ASSERT(species.phi_wall_lo.phi != 0); + TEST_ASSERT(species.phi_wall_up.phi != 0); + TEST_ASSERT(species.phi_wall_lo.projector != 0); + TEST_ASSERT(species.phi_wall_up.projector != 0); + TEST_ASSERT(species.phi_wall_lo.advance_func != 0); + TEST_ASSERT(species.phi_wall_up.advance_func != 0); + check_profile(&app, species.phi_wall_lo.phi, &lower_ctx, 0.0); + check_profile(&app, species.phi_wall_up.phi, &upper_ctx, 0.0); + + double tm = 0.625; + gk_species_phi_wall_advance(&app, &species.phi_wall_lo, tm); + check_profile(&app, species.phi_wall_lo.phi, &lower_ctx, tm); + check_profile(&app, species.phi_wall_up.phi, &upper_ctx, 0.0); + gk_species_phi_wall_advance(&app, &species.phi_wall_up, tm); + check_profile(&app, species.phi_wall_up.phi, &upper_ctx, tm); + + gk_species_phi_wall_release(&app, &species.phi_wall_lo); + gk_species_phi_wall_release(&app, &species.phi_wall_up); +} + +static void +test_grounded_and_non_sheath_walls(void) +{ + // A sheath with no auxiliary profile represents a grounded wall, so every + // DG coefficient is exactly zero. A non-sheath boundary needs no wall- + // potential array or projector at all. + gkyl_gyrokinetic_app app = make_app(); + struct gk_species species = { + .lower_bc[0] = { + .type = GKYL_BC_GK_SPECIES_SHEATH, + }, + .upper_bc[0] = { + .type = GKYL_BC_GK_SPECIES_REFLECT, + }, + }; + + gk_species_phi_wall_init(&app, &species.lower_bc[0], &species.phi_wall_lo); + gk_species_phi_wall_init(&app, &species.upper_bc[0], &species.phi_wall_up); + TEST_ASSERT(species.phi_wall_lo.phi != 0); + TEST_CHECK(species.phi_wall_lo.phi_host == species.phi_wall_lo.phi); + TEST_CHECK(species.phi_wall_lo.projector == 0); + TEST_ASSERT(species.phi_wall_lo.advance_func != 0); + TEST_CHECK(species.phi_wall_up.phi == 0); + TEST_CHECK(species.phi_wall_up.phi_host == 0); + TEST_CHECK(species.phi_wall_up.projector == 0); + TEST_ASSERT(species.phi_wall_up.advance_func != 0); + + gk_species_phi_wall_advance(&app, &species.phi_wall_lo, 1.0); + gk_species_phi_wall_advance(&app, &species.phi_wall_up, 1.0); + + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &app.local_ext); + while (gkyl_range_iter_next(&iter)) { + long lidx = gkyl_range_idx(&app.local_ext, iter.idx); + const double *phi_c = gkyl_array_cfetch(species.phi_wall_lo.phi, lidx); + for (int k=0; k -#include +#include +#include +#include + #include #include -#include +#include +#include +#include #include -#include #include -#include -#include +#include #include -#include -#include -#include -#include -#include struct loss_cone_mask_test_ctx { - int cdim; // Configuration space dimensionality. - double eV; // Elementary charge. - double R_m; // Mirror ratio. - double B_m; // Maximum magnetic field amplitude. - double z_m; // Location of B_m. - double mass, charge; // Species mass and charge. - double n0, T0, B0; // Reference parameters. - double phi_fac; // phi(z=0) = phi_fac*T0/e; - double z_max, vpar_max, mu_max; // Upper grid extents. - int Nz, Nvpar, Nmu; // Number of cells in each direction. - enum gkyl_quad_type quad_type; // Type of quadrature/nodes. - int num_quad; // Number of quadrature points to use in projection, 1 or p+1. - bool cellwise_trap_loss; // Whether a whole cell is either trapped or lost. + int cdim; + double eV; + double R_m; + double B_m; + double z_m; + double mass, charge; + double n0, T0, B0; + double phi_fac; + double z_max, vpar_max, mu_max; + int Nz, Nvpar, Nmu; }; -// allocate array (filled with zeros) static struct gkyl_array* mkarr(bool use_gpu, long nc, long size) { - struct gkyl_array* a = use_gpu? gkyl_array_cu_dev_new(GKYL_DOUBLE, nc, size) - : gkyl_array_new(GKYL_DOUBLE, nc, size); - return a; + return use_gpu ? gkyl_array_cu_dev_new(GKYL_DOUBLE, nc, size) + : gkyl_array_new(GKYL_DOUBLE, nc, size); } -void -mapc2p_3x(double t, const double *xc, double* GKYL_RESTRICT xp, void *ctx) +static void +bmag_func_1x(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) { - xp[0] = xc[0]; xp[1] = xc[1]; xp[2] = xc[2]; + double z = xc[0]; + struct loss_cone_mask_test_ctx *params = ctx; + + fout[0] = params->B_m + * (1.0 - ((params->R_m - 1.0) / params->R_m) * pow(cos(z), 2.0)); } -void -bfield_func_3x(double t, const double *xc, double* GKYL_RESTRICT fout, void *ctx) +static void +phi_func_1x_zero(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) { - double x = xc[0], y = xc[1], z = xc[2]; - - struct loss_cone_mask_test_ctx *params = ctx; - double R_m = params->R_m; // Mirror ratio. - double B_m = params->B_m; // Maximum magnetic field amplitude. - fout[0] = 0.0; - fout[1] = 0.0; - fout[2] = B_m * (1.0 - ((R_m-1.0)/R_m)*pow(cos(z), 2.0)); -// fout[0] = (B_m/R_m) * (1.0 + (R_m-1.0)*pow(sin(z), 2.0)); } -void -phi_func_1x(double t, const double *xc, double* GKYL_RESTRICT fout, void *ctx) +static void +phi_func_1x_nonzero(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) { double z = xc[0]; - struct loss_cone_mask_test_ctx *params = ctx; - double phi_fac = params->phi_fac; - double T0 = params->T0; - double eV = params->eV; - fout[0] = 0.0; //0.5 * phi_fac*T0/eV * (1.0 + cos(z)); + // fout[0] = params->phi_fac * params->T0 / params->eV * (1.0 - pow(z / params->z_max, 2.0)); + fout[0] = params->phi_fac * params->T0 / params->eV * (1.0 - pow(cos(4*z), 2.0)); } -void -mask_ref_1x2v(double t, const double *xc, double* GKYL_RESTRICT fout, void *ctx) +static double +analytic_bmag(const struct loss_cone_mask_test_ctx *ctx, double z) { - double z = xc[0], vpar = xc[1], mu = xc[2]; - struct loss_cone_mask_test_ctx *params = ctx; + return ctx->B_m * (1.0 - ((ctx->R_m - 1.0) / ctx->R_m) * pow(cos(z), 2.0)); +} - double z_m = params->z_m; - double mass = params->mass; - double charge = params->charge; - - double phi, phi_m; - phi_func_1x(t, xc, &phi, ctx); - phi_func_1x(t, &z_m, &phi_m, ctx); - - double bfield[3], bmag; - double zinfl[3] = {0.0}, z_minfl[3] = {0.0}; - zinfl[2] = z, z_minfl[2] = z_m; - bfield_func_3x(t, zinfl, bfield, ctx); - bmag = bfield[2]; - - double bfield_m[3], bmag_m; - bfield_func_3x(t, z_minfl, bfield_m, ctx); - bmag_m = bfield_m[2]; - - // mu_bound = (0.5*m*vpar^2+q*(phi-phi_m))/(B*(B_max/B-1)) - double mu_bound = (0.5*mass*pow(vpar,2)+charge*(phi-phi_m))/(bmag*(bmag_m/bmag-1)); - if (mu_bound < mu && fabs(z) < z_m) - fout[0] = 1.0; - else - fout[0] = 0; +static double +analytic_phi(const struct loss_cone_mask_test_ctx *ctx, double z, + bool use_nonzero_phi) +{ + return use_nonzero_phi + ? ctx->phi_fac * ctx->T0 / ctx->eV * (1.0 - pow(cos(4.0 * z), 2.0)) + : 0.0; } -void -test_1x2v_gk(int poly_order, bool use_gpu) +static double +analytic_effective_potential(const struct loss_cone_mask_test_ctx *ctx, + double z, double mu, bool use_nonzero_phi) { + return mu * analytic_bmag(ctx, z) + + ctx->charge * analytic_phi(ctx, z, use_nonzero_phi); +} + +/** + * Build the expected mask directly from the analytic B(z), phi(z), Cartesian + * velocity-cell endpoints, and H=mv_parallel^2/2+mu*B+q*phi. The P1 fields + * are nodally projected, so their cell-corner values equal these analytic + * functions. This oracle deliberately does not evaluate the production DG + * arrays or call any loss-cone helper. + */ +static void +build_analytic_reference_mask_1x2v(const struct gkyl_rect_grid *grid, + const struct gkyl_range *phase_range, const struct loss_cone_mask_test_ctx *ctx, + bool use_nonzero_phi, struct gkyl_array *mask_ref) +{ + struct gkyl_range corner_range; + gkyl_range_init_from_shape(&corner_range, 3, (int[3]) { 2, 2, 2 }); + + struct gkyl_range_iter phase_iter; + gkyl_range_iter_init(&phase_iter, phase_range); + while (gkyl_range_iter_next(&phase_iter)) { + bool cell_trapped = true; + struct gkyl_range_iter corner_iter; + gkyl_range_iter_init(&corner_iter, &corner_range); + while (cell_trapped && gkyl_range_iter_next(&corner_iter)) { + int z_vertex = phase_iter.idx[0] - 1 + corner_iter.idx[0]; + double z = grid->lower[0] + z_vertex * grid->dx[0]; + double vpar = grid->lower[1] + + (phase_iter.idx[1] - 1 + corner_iter.idx[1]) * grid->dx[1]; + double mu = grid->lower[2] + + (phase_iter.idx[2] - 1 + corner_iter.idx[2]) * grid->dx[2]; + double u_curr = analytic_effective_potential(ctx, z, mu, use_nonzero_phi); + double h_curr = 0.5 * ctx->mass * vpar * vpar + u_curr; + + double barrier_left = -DBL_MAX, barrier_right = -DBL_MAX; + for (int vertex = 0; vertex <= ctx->Nz; ++vertex) { + double z_scan = grid->lower[0] + vertex * grid->dx[0]; + double u_scan = analytic_effective_potential(ctx, z_scan, mu, + use_nonzero_phi); + if (vertex <= z_vertex) { + barrier_left = GKYL_MAX2(barrier_left, u_scan); + } + if (vertex >= z_vertex) { + barrier_right = GKYL_MAX2(barrier_right, u_scan); + } + } + + cell_trapped = h_curr < GKYL_MIN2(barrier_left, barrier_right); + } + + long linidx_phase = gkyl_range_idx(phase_range, phase_iter.idx); + double *m = gkyl_array_fetch(mask_ref, linidx_phase); + m[0] = cell_trapped ? 1.0 : 0.0; + } +} + +static void +run_case_1x2v(int poly_order, bool use_gpu, bool use_nonzero_phi) +{ double eV = GKYL_ELEMENTARY_CHARGE; double mass_proton = GKYL_PROTON_MASS; - // Set reference parameters. struct loss_cone_mask_test_ctx ctx = { .cdim = 1, .eV = eV, .R_m = 8.0, .B_m = 4.0, - .z_m = M_PI/2.0, - .mass = 2.014*mass_proton, + .z_m = GKYL_PI / 2.0, + .mass = 2.014 * mass_proton, .charge = eV, .n0 = 1e18, - .T0 = 100*eV, - .phi_fac = 3.0, - .z_max = M_PI, - .Nz = 8, - .Nvpar = 8, - .Nmu = 4, - .quad_type = GKYL_GAUSS_LOBATTO_QUAD, - .num_quad = 2, - .cellwise_trap_loss = true, + .T0 = 100 * eV, + .phi_fac = 5.0, + .z_max = GKYL_PI - 0.5, + .Nz = 64, + .Nvpar = 16, + .Nmu = 16, }; - ctx.B0 = ctx.B_m/2.0; - ctx.vpar_max = 6.0*sqrt(ctx.T0/ctx.mass); - ctx.mu_max = 0.5*ctx.mass*pow(ctx.vpar_max,2)/ctx.B0; - - double mass = ctx.mass; - double lower[] = {-ctx.z_max, -ctx.vpar_max, 0.0}, upper[] = {ctx.z_max, ctx.vpar_max, ctx.mu_max}; - int cells[] = {ctx.Nz, ctx.Nvpar, ctx.Nmu}; - const int ndim = sizeof(cells)/sizeof(cells[0]); + ctx.B0 = ctx.B_m / 2.0; + ctx.vpar_max = 6.0 * sqrt(ctx.T0 / ctx.mass); + ctx.mu_max = 0.5 * ctx.mass * pow(ctx.vpar_max, 2) / ctx.B0; + + double lower[] = { -ctx.z_max, -ctx.vpar_max, 0.0 }; + double upper[] = { ctx.z_max, ctx.vpar_max, ctx.mu_max }; + int cells[] = { ctx.Nz, ctx.Nvpar, ctx.Nmu }; + const int ndim = sizeof(cells) / sizeof(cells[0]); const int cdim = ctx.cdim; - const int vdim = ndim-ctx.cdim; + const int vdim = ndim - cdim; - // Grids. double lower_conf[cdim], upper_conf[cdim]; int cells_conf[cdim]; - for (int d=0; dncomp, phi->size) - : gkyl_array_acquire(phi); - - gkyl_eval_on_nodes *evphi = gkyl_eval_on_nodes_new(&grid_conf, &basis_conf, 1, phi_func_1x, &ctx); + struct gkyl_array *phi_ho = mkarr(false, basis_conf.num_basis, local_ext_conf.volume); + + // Nodal P1 projection makes the DG corner values exactly equal to the + // analytic B(z) values used by the independent reference below. + gkyl_eval_on_nodes *evbmag = gkyl_eval_on_nodes_new(&grid_conf, &basis_conf, + 1, bmag_func_1x, &ctx); + gkyl_eval_on_nodes_advance(evbmag, 0.0, &local_conf, bmag_ho); + gkyl_eval_on_nodes_release(evbmag); + gkyl_array_copy(bmag, bmag_ho); + + evalf_t phi_func = use_nonzero_phi ? phi_func_1x_nonzero : phi_func_1x_zero; + gkyl_eval_on_nodes *evphi = gkyl_eval_on_nodes_new(&grid_conf, &basis_conf, 1, phi_func, &ctx); gkyl_eval_on_nodes_advance(evphi, 0.0, &local_conf, phi_ho); gkyl_eval_on_nodes_release(evphi); gkyl_array_copy(phi, phi_ho); - // Location of the mirror throat. - double bmag_max_loc_ho[] = {ctx.z_m}; - double *bmag_max_loc; - if (use_gpu) { - bmag_max_loc = gkyl_cu_malloc(sizeof(double)); - gkyl_cu_memcpy(bmag_max_loc, bmag_max_loc_ho, sizeof(double), GKYL_CU_MEMCPY_H2D); - } - else { - bmag_max_loc = gkyl_malloc(sizeof(double)); - memcpy(bmag_max_loc, bmag_max_loc_ho, sizeof(double)); - } + struct gkyl_loss_cone_mask_gyrokinetic_inp inp_proj = { + .conf_basis = &basis_conf, + .vel_map = gvm, + .use_gpu = use_gpu, + .mass = ctx.mass, + .charge = ctx.charge, + }; + struct gkyl_loss_cone_mask_gyrokinetic *proj_mask = + gkyl_loss_cone_mask_gyrokinetic_inew(&inp_proj); + + struct gkyl_array *mask = mkarr(use_gpu, 1, local_ext.volume); + struct gkyl_array *mask_ho = mkarr(false, 1, local_ext.volume); + struct gkyl_array *mask_cpu = use_gpu ? mkarr(false, 1, local_ext.volume) : 0; + struct gkyl_array *mask_ref = mkarr(false, 1, local_ext.volume); + + gkyl_loss_cone_mask_gyrokinetic_advance(proj_mask, &local, &local_conf, + bmag, phi, 0, 0, mask); + gkyl_array_copy(mask_ho, mask); - // Get the magnetic field at the mirror throat. - double bfield_max_ho[3], bmag_max_ho[1]; - double xc_infl[] = {0.0,0.0,ctx.z_m}; - bfield_func_3x(0.0, xc_infl, bfield_max_ho, &ctx); - bmag_max_ho[0] = bfield_max_ho[2]; - double *bmag_max; + gkyl_array_clear(mask_ref, 0.0); + build_analytic_reference_mask_1x2v(&grid, &local, &ctx, use_nonzero_phi, + mask_ref); + +#ifdef GKYL_HAVE_CUDA if (use_gpu) { - bmag_max = gkyl_cu_malloc(sizeof(double)); - gkyl_cu_memcpy(bmag_max, bmag_max_ho, sizeof(double), GKYL_CU_MEMCPY_H2D); + struct gkyl_loss_cone_mask_gyrokinetic_inp inp_proj_cpu = { + .conf_basis = &basis_conf, + .vel_map = gvm_ho, + .use_gpu = false, + .mass = ctx.mass, + .charge = ctx.charge, + }; + struct gkyl_loss_cone_mask_gyrokinetic *proj_mask_cpu = + gkyl_loss_cone_mask_gyrokinetic_inew(&inp_proj_cpu); + + gkyl_loss_cone_mask_gyrokinetic_advance(proj_mask_cpu, &local, &local_conf, + bmag_ho, phi_ho, 0, 0, mask_cpu); + + int gpu_cpu_mismatches = 0; + struct gkyl_range_iter iter_cmp; + gkyl_range_iter_init(&iter_cmp, &local); + while (gkyl_range_iter_next(&iter_cmp)) { + long linidx = gkyl_range_idx(&local, iter_cmp.idx); + const double *mg = gkyl_array_cfetch(mask_ho, linidx); + const double *mc = gkyl_array_cfetch(mask_cpu, linidx); + + bool same = fabs(mg[0] - mc[0]) < 1e-12; + TEST_CHECK(same); + if (!same && gpu_cpu_mismatches < 8) { + printf("gpu/cpu mismatch idx=(%d,%d,%d): gpu=%g cpu=%g\n", + iter_cmp.idx[0], iter_cmp.idx[1], iter_cmp.idx[2], mg[0], mc[0]); + gpu_cpu_mismatches++; + } + } + + gkyl_loss_cone_mask_gyrokinetic_release(proj_mask_cpu); } - else { - bmag_max = gkyl_malloc(sizeof(double)); - memcpy(bmag_max, bmag_max_ho, sizeof(double)); +#endif + + int mismatches = 0; + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &local); + while (gkyl_range_iter_next(&iter)) { + long linidx = gkyl_range_idx(&local, iter.idx); + const double *m = gkyl_array_cfetch(mask_ho, linidx); + const double *r = gkyl_array_cfetch(mask_ref, linidx); + + bool ok = fabs(m[0] - r[0]) < 1e-12; + TEST_CHECK(ok); + if (!ok && mismatches < 8) { + printf("mask mismatch idx=(%d,%d,%d): got=%g ref=%g\n", + iter.idx[0], iter.idx[1], iter.idx[2], m[0], r[0]); + mismatches++; + } } - // Get the potential at the mirror throat (z=pi/2). - double phi_m_ho[1]; - double xc[] = {ctx.z_m}; - phi_func_1x(0.0, xc, phi_m_ho, &ctx); - double *phi_m; - if (use_gpu) { - phi_m = gkyl_cu_malloc(sizeof(double)); - gkyl_cu_memcpy(phi_m, phi_m_ho, sizeof(double), GKYL_CU_MEMCPY_H2D); + gkyl_array_release(bmag); + gkyl_array_release(bmag_ho); + gkyl_array_release(phi); + gkyl_array_release(phi_ho); + gkyl_array_release(mask); + gkyl_array_release(mask_ho); + if (mask_cpu) { + gkyl_array_release(mask_cpu); } - else { - phi_m = gkyl_malloc(sizeof(double)); - memcpy(phi_m, phi_m_ho, sizeof(double)); + gkyl_array_release(mask_ref); + + gkyl_loss_cone_mask_gyrokinetic_release(proj_mask); + gkyl_velocity_map_release(gvm); + if (gvm_ho) { + gkyl_velocity_map_release(gvm_ho); } +} + +struct constant_boundary_case { + const char *name; + double charge; + double phi_plasma; + double phi_wall_lo; + double phi_wall_up; + bool use_wall_lo; + bool use_wall_up; + enum gkyl_gk_trapped_passing_orbit_type lower_orbit; + enum gkyl_gk_trapped_passing_orbit_type upper_orbit; +}; - // Basis used to project the mask. - struct gkyl_basis basis_mask; - if (ctx.num_quad == 1 || ctx.cellwise_trap_loss) - gkyl_cart_modal_serendip(&basis_mask, ndim, 0); - else { - if (poly_order == 1) - gkyl_cart_modal_gkhybrid(&basis_mask, cdim, vdim); - else - gkyl_cart_modal_serendip(&basis_mask, ndim, poly_order); +static void +set_constant_1x_p1(struct gkyl_array *field, const struct gkyl_range *range, + double value) +{ + gkyl_array_clear(field, 0.0); + + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, range); + while (gkyl_range_iter_next(&iter)) { + long linidx = gkyl_range_idx(range, iter.idx); + double *field_d = gkyl_array_fetch(field, linidx); + field_d[0] = sqrt(2.0) * value; } +} - // Create mask array. - struct gkyl_array *mask = mkarr(use_gpu, basis_mask.num_basis, local_ext.volume); - struct gkyl_array *mask_ho = use_gpu? mkarr(false, mask->ncomp, mask->size) - : gkyl_array_acquire(mask); +static double +constant_boundary_energy(double charge, double phi_plasma, double phi_wall, + bool use_wall, enum gkyl_gk_trapped_passing_orbit_type orbit) +{ + if (orbit == GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL) { + return DBL_MAX; + } + return orbit == GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH && use_wall + ? GKYL_MAX2(0.0, charge * (phi_wall - phi_plasma)) : 0.0; +} - // Project the loss cone mask. - struct gkyl_loss_cone_mask_gyrokinetic_inp inp_proj = { - .phase_grid = &grid, - .conf_basis = &basis_conf, - .phase_basis = &basis, - .conf_range = &local_conf, - .conf_range_ext = &local_ext_conf, - .vel_range = &local_vel, - .vel_map = gvm, - .bmag = gk_geom->geo_int.bmag, - .bmag_max = bmag_max, - .bmag_max_loc = bmag_max_loc, - .mass = ctx.mass, - .charge = ctx.charge, - .qtype = ctx.quad_type, - .num_quad = ctx.num_quad, - .cellwise_trap_loss = ctx.cellwise_trap_loss, - .use_gpu = use_gpu, +static void +run_constant_boundary_cases(bool use_gpu) +{ + // With constant B and phi, mu*B cancels between H and each wall barrier. + // These cases therefore have an exact, independent criterion in each + // v_parallel cell: every velocity endpoint must satisfy K < Delta U at + // both boundaries. + const struct constant_boundary_case cases[] = { + { + .name = "grounded_wall_electron", + .charge = -1.0, .phi_plasma = 4.0, + .phi_wall_lo = 0.0, .phi_wall_up = 0.0, + .use_wall_lo = true, .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + .use_wall_up = true, .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + }, + { + .name = "grounded_wall_ion_no_barrier", + .charge = 1.0, .phi_plasma = 4.0, + .phi_wall_lo = 0.0, .phi_wall_up = 0.0, + .use_wall_lo = true, .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + .use_wall_up = true, .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + }, + { + .name = "grounded_wall_ion_reversed_phi", + .charge = 1.0, .phi_plasma = -4.0, + .phi_wall_lo = 0.0, .phi_wall_up = 0.0, + .use_wall_lo = true, .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + .use_wall_up = true, .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + }, + { + .name = "asymmetric_biased_wall_electron", + .charge = -1.0, .phi_plasma = 4.0, + .phi_wall_lo = 0.0, .phi_wall_up = 3.0, + .use_wall_lo = true, .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + .use_wall_up = true, .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + }, + { + .name = "asymmetric_biased_wall_ion", + .charge = 1.0, .phi_plasma = 0.0, + .phi_wall_lo = 4.0, .phi_wall_up = 1.0, + .use_wall_lo = true, .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + .use_wall_up = true, .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + }, + { + // This is the asymmetric electron case shifted everywhere by +7 V. + .name = "gauge_shifted_asymmetric_electron", + .charge = -1.0, .phi_plasma = 11.0, + .phi_wall_lo = 7.0, .phi_wall_up = 10.0, + .use_wall_lo = true, .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + .use_wall_up = true, .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + }, + { + // Delta U=2 and a v_parallel endpoint is exactly |v|=2. Equality + // reaches the wall and must be classified as passing/absorbed. + .name = "sheath_cutoff_equality", + .charge = -1.0, .phi_plasma = 2.0, + .phi_wall_lo = 0.0, .phi_wall_up = 0.0, + .use_wall_lo = true, .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + .use_wall_up = true, .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + }, + { + .name = "passing_orbits", + .charge = -1.0, .phi_plasma = 4.0, + .phi_wall_lo = 0.0, .phi_wall_up = 0.0, + .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_PASSING, + .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_PASSING, + }, + { + .name = "passing_and_wall_trapped_orbits", + .charge = -1.0, .phi_plasma = 4.0, + .phi_wall_lo = 0.0, .phi_wall_up = 0.0, + .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_PASSING, + .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL, + }, + { + .name = "wall_trapped_and_sheath_trapped_orbits", + .charge = -1.0, .phi_plasma = 4.0, + .phi_wall_lo = 0.0, .phi_wall_up = 0.0, + .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL, + .use_wall_up = true, .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + }, + { + .name = "sheath_trapped_and_wall_trapped_orbits", + .charge = -1.0, .phi_plasma = 4.0, + .phi_wall_lo = 0.0, .phi_wall_up = 0.0, + .use_wall_lo = true, .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, + .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL, + }, + { + .name = "wall_trapped_orbits", + .charge = -1.0, .phi_plasma = 4.0, + .phi_wall_lo = 0.0, .phi_wall_up = 0.0, + .lower_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL, + .upper_orbit = GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL, + }, }; - struct gkyl_loss_cone_mask_gyrokinetic *proj_mask = gkyl_loss_cone_mask_gyrokinetic_inew( &inp_proj ); - gkyl_loss_cone_mask_gyrokinetic_advance(proj_mask, &local, &local_conf, phi, phi_m, mask); + double lower[] = { -1.0, -4.0, 0.0 }; + double upper[] = { 1.0, 4.0, 1.0 }; + int cells[] = { 2, 8, 1 }; + double lower_conf[] = { lower[0] }, upper_conf[] = { upper[0] }; + double lower_vel[] = { lower[1], lower[2] }; + double upper_vel[] = { upper[1], upper[2] }; + int cells_conf[] = { cells[0] }, cells_vel[] = { cells[1], cells[2] }; - gkyl_array_copy(mask_ho, mask); + struct gkyl_rect_grid grid, grid_conf, grid_vel; + gkyl_rect_grid_init(&grid, 3, lower, upper, cells); + gkyl_rect_grid_init(&grid_conf, 1, lower_conf, upper_conf, cells_conf); + gkyl_rect_grid_init(&grid_vel, 2, lower_vel, upper_vel, cells_vel); - // Project expected mask. - struct gkyl_array *mask_ref_ho = mkarr(false, basis_mask.num_basis, local_ext.volume); - gkyl_proj_on_basis *evmask_ref = gkyl_proj_on_basis_new(&grid, &basis_mask, basis_mask.poly_order+1, 1, mask_ref_1x2v, &ctx); - gkyl_proj_on_basis_advance(evmask_ref, 0.0, &local, mask_ref_ho); - gkyl_proj_on_basis_release(evmask_ref); - if (ctx.num_quad == 1) { - // Rescale to deal with normalization. - gkyl_array_scale(mask_ref_ho, 1.0/pow(sqrt(2.0),cdim+vdim)); - } + struct gkyl_basis basis_conf; + gkyl_cart_modal_serendip(&basis_conf, 1, 1); -// // values to compare at index (1, 9, 9) [remember, lower-left index is (1,1,1)] -// double p1_vals[] = { -// 7.2307139183122714e-03, 0.0000000000000000e+00, 1.9198293226362615e-04, -7.7970439910196674e-04, 0.0000000000000000e+00, 0.0000000000000000e+00, -// -2.0701958137127286e-05, 0.0000000000000000e+00, -1.4953406100022537e-04, 0.0000000000000000e+00, 1.6124599381836546e-05, 0.0000000000000000e+00, -// -8.2719200283232917e-19, 0.0000000000000000e+00, -3.4806248503322844e-20, 0.0000000000000000e+00, }; -// double p2_vals[] = { -// 7.2307468609012666e-03, 0.0000000000000000e+00, 1.9198380692343289e-04, -7.8092230706225602e-04, 0.0000000000000000e+00, 0.0000000000000000e+00, -// -2.0734294852987710e-05, 3.6591823321385775e-18, -1.4953474226616330e-04, 3.7739922227981074e-05, 0.0000000000000000e+00, 7.0473141211557788e-19, -// 0.0000000000000000e+00, -4.8789097761847700e-19, 1.6149786206441256e-05, 0.0000000000000000e+00, 1.0020339643610290e-06, 5.4210108624275222e-20, -// 0.0000000000000000e+00, 0.0000000000000000e+00 }; -// -// const double *fv = gkyl_array_cfetch(distf, gkyl_range_idx(&local_ext, (int[3]) { 1, 9, 9 })); -// if (poly_order == 1) { -// for (int i=0; iphi_plasma); + set_constant_1x_p1(phi_wall_lo_ho, &local_ext_conf, test->phi_wall_lo); + set_constant_1x_p1(phi_wall_up_ho, &local_ext_conf, test->phi_wall_up); + gkyl_array_copy(phi, phi_ho); + gkyl_array_copy(phi_wall_lo, phi_wall_lo_ho); + gkyl_array_copy(phi_wall_up, phi_wall_up_ho); + + struct gkyl_loss_cone_mask_gyrokinetic *up = + gkyl_loss_cone_mask_gyrokinetic_inew(&(struct gkyl_loss_cone_mask_gyrokinetic_inp) { + .conf_basis = &basis_conf, + .vel_map = gvm, + .use_gpu = use_gpu, + .mass = 1.0, + .charge = test->charge, + .lower_orbit = test->lower_orbit, + .upper_orbit = test->upper_orbit, + }); + + const struct gkyl_array *wall_lo = test->use_wall_lo ? phi_wall_lo : 0; + const struct gkyl_array *wall_up = test->use_wall_up ? phi_wall_up : 0; + gkyl_loss_cone_mask_gyrokinetic_advance(up, &local, &local_conf, + bmag, phi, wall_lo, wall_up, mask); + gkyl_array_copy(mask_ho, mask); + + double barrier_lo = constant_boundary_energy(test->charge, test->phi_plasma, + test->phi_wall_lo, test->use_wall_lo, test->lower_orbit); + double barrier_up = constant_boundary_energy(test->charge, test->phi_plasma, + test->phi_wall_up, test->use_wall_up, test->upper_orbit); + + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &local); + while (gkyl_range_iter_next(&iter)) { + int ivpar = iter.idx[1]; + double vpar_lo = grid.lower[1] + (ivpar - 1) * grid.dx[1]; + double vpar_up = vpar_lo + grid.dx[1]; + double kinetic_max = 0.5 * GKYL_MAX2(vpar_lo * vpar_lo, vpar_up * vpar_up); + double expected = kinetic_max < barrier_lo && kinetic_max < barrier_up ? 1.0 : 0.0; + + long linidx = gkyl_range_idx(&local, iter.idx); + const double *mask_d = gkyl_array_cfetch(mask_ho, linidx); + double actual = mask_d[0]; + bool ok = fabs(actual - expected) < 1e-12; + TEST_CHECK(ok); + if (!ok) { + TEST_MSG("%s idx=(%d,%d,%d): got=%g expected=%g Kmax=%g barriers=(%g,%g)", + test->name, iter.idx[0], iter.idx[1], iter.idx[2], actual, expected, + kinetic_max, barrier_lo, barrier_up); + } + } + + gkyl_loss_cone_mask_gyrokinetic_release(up); } - gkyl_array_release(phi); - gkyl_array_release(phi_ho); - gkyl_array_release(mask); + + gkyl_array_release(bmag); + gkyl_array_release(phi); + gkyl_array_release(phi_wall_lo); + gkyl_array_release(phi_wall_up); + gkyl_array_release(mask); + gkyl_array_release(bmag_ho); + gkyl_array_release(phi_ho); + gkyl_array_release(phi_wall_lo_ho); + gkyl_array_release(phi_wall_up_ho); gkyl_array_release(mask_ho); - gkyl_array_release(mask_ref_ho); - gkyl_loss_cone_mask_gyrokinetic_release(proj_mask); gkyl_velocity_map_release(gvm); - gkyl_gk_geometry_release(gk_geom); - gkyl_position_map_release(pmap); +} -#ifdef GKYL_HAVE_CUDA - if (use_gpu) { - gkyl_cu_free(basis_on_dev); - gkyl_cu_free(basis_on_dev_conf); - } -#endif +void +test_1x2v_p1_gk_ho(void) +{ + // Magnetic-mirror barriers only: compare every host mask cell with the + // analytic Hamiltonian reference built above. + run_case_1x2v(1, false, false); +} + +void +test_1x2v_p1_nonzero_phi_gk_ho(void) +{ + // Add a spatially varying electrostatic potential to check that q*phi is + // included in both directional barriers. + run_case_1x2v(1, false, true); } -void test_1x2v_p1_gk_ho() { test_1x2v_gk(1, false); } +void +test_1x2v_p1_constant_boundaries_gk_ho(void) +{ + // Check exact wall-potential thresholds, open/closed trajectories, gauge + // invariance, and equality at the absorbing cutoff. + run_constant_boundary_cases(false); +} #ifdef GKYL_HAVE_CUDA -void test_1x2v_p1_gk_dev() { test_1x2v_gk(1, true); } +// The device cases use the same independent expectations as the host cases +// and also compare the device mask cell-by-cell with a host updater run. +void +test_1x2v_p1_gk_dev(void) +{ + run_case_1x2v(1, true, false); +} + +void +test_1x2v_p1_nonzero_phi_gk_dev(void) +{ + run_case_1x2v(1, true, true); +} + +void +test_1x2v_p1_constant_boundaries_gk_dev(void) +{ + run_constant_boundary_cases(true); +} #endif TEST_LIST = { { "test_1x2v_p1_gk_ho", test_1x2v_p1_gk_ho }, - + { "test_1x2v_p1_nonzero_phi_gk_ho", test_1x2v_p1_nonzero_phi_gk_ho }, + { "test_1x2v_p1_constant_boundaries_gk_ho", test_1x2v_p1_constant_boundaries_gk_ho }, #ifdef GKYL_HAVE_CUDA { "test_1x2v_p1_gk_dev", test_1x2v_p1_gk_dev }, + { "test_1x2v_p1_nonzero_phi_gk_dev", test_1x2v_p1_nonzero_phi_gk_dev }, + { "test_1x2v_p1_constant_boundaries_gk_dev", test_1x2v_p1_constant_boundaries_gk_dev }, #endif { NULL, NULL }, }; diff --git a/gyrokinetic/unit/mctest_loss_cone_mask_gyrokinetic.c b/gyrokinetic/unit/mctest_loss_cone_mask_gyrokinetic.c new file mode 100644 index 0000000000..34200b1bd6 --- /dev/null +++ b/gyrokinetic/unit/mctest_loss_cone_mask_gyrokinetic.c @@ -0,0 +1,225 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef GKYL_HAVE_MPI +#include +#endif + +struct lc_ctx { + double z_max; + double b_m; + double r_m; + double t0; + double eV; + double phi_fac; +}; + +typedef void (*evalf_t)(double t, const double *xn, double *fout, void *ctx); + +static struct gkyl_array* +mkarr(long nc, long size) +{ + return gkyl_array_new(GKYL_DOUBLE, nc, size); +} + +static struct gkyl_comm* +comm_new(struct gkyl_rect_decomp *decomp) +{ +#ifdef GKYL_HAVE_MPI + return gkyl_mpi_comm_new(&(struct gkyl_mpi_comm_inp) { + .mpi_comm = MPI_COMM_WORLD, + .decomp = decomp, + }); +#else + return gkyl_null_comm_inew(&(struct gkyl_null_comm_inp) { .use_gpu = false }); +#endif +} + +static void +bmag_func_1x(double t, const double *xn, double *fout, void *ctx) +{ + struct lc_ctx *p = ctx; + double z = xn[0]; + fout[0] = p->b_m * (1.0 - ((p->r_m - 1.0) / p->r_m) * pow(cos(z), 2.0)); +} + +static void +phi_func_1x(double t, const double *xn, double *fout, void *ctx) +{ + struct lc_ctx *p = ctx; + double z = xn[0]; + fout[0] = p->phi_fac * p->t0 / p->eV * (1.0 - pow(cos(4.0 * z), 2.0)); +} + +static void +eval_dg_on_range(const struct gkyl_rect_grid *grid, const struct gkyl_basis *basis, + const struct gkyl_range *range, evalf_t eval, void *ctx, struct gkyl_array *out) +{ + struct gkyl_eval_on_nodes *ev = gkyl_eval_on_nodes_new(grid, basis, 1, eval, ctx); + gkyl_eval_on_nodes_advance(ev, 0.0, range, out); + gkyl_eval_on_nodes_release(ev); +} + +static void +test_loss_cone_mask_parallel_4dom(void) +{ + // The four ranks each evaluate one z slab using globally gathered B and + // phi. The independently evaluated single-domain result is the oracle; an + // exact cell-by-cell match checks that decomposition and allgather do not + // change the mask. + int rank = 0, size = 1; +#ifdef GKYL_HAVE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); +#endif + + if (size != 4) { + if (rank == 0) { + TEST_MSG("mctest_loss_cone_mask_gyrokinetic requires 4 MPI ranks, got %d", size); + } + return; + } + + const int cdim = 1, vdim = 2, pdim = 3; + const int nz = 64, nvpar = 16, nmu = 16; + const int cells_conf[] = { nz }; + const int cells_phase[] = { nz, nvpar, nmu }; + const int cells_vel[] = { nvpar, nmu }; + const int cuts[] = { 4 }; + const double z_max = GKYL_PI - 0.5; + const double lower_phase[] = { -z_max, -6.0, 0.0 }; + const double upper_phase[] = { z_max, 6.0, 18.0 }; + const double lower_conf[] = { -z_max }; + const double upper_conf[] = { z_max }; + const double lower_vel[] = { -6.0, 0.0 }; + const double upper_vel[] = { 6.0, 18.0 }; + + struct gkyl_rect_grid grid_conf, grid_phase, grid_vel; + gkyl_rect_grid_init(&grid_conf, cdim, lower_conf, upper_conf, cells_conf); + gkyl_rect_grid_init(&grid_phase, pdim, lower_phase, upper_phase, cells_phase); + gkyl_rect_grid_init(&grid_vel, vdim, lower_vel, upper_vel, cells_vel); + + struct gkyl_range conf_global, phase_global, vel_global; + gkyl_create_global_range(cdim, cells_conf, &conf_global); + gkyl_create_global_range(pdim, cells_phase, &phase_global); + gkyl_create_global_range(vdim, cells_vel, &vel_global); + + struct gkyl_rect_decomp *conf_decomp = gkyl_rect_decomp_new_from_cuts(cdim, cuts, &conf_global); + int phase_cuts[] = { 4, 1, 1 }; + struct gkyl_rect_decomp *phase_decomp = gkyl_rect_decomp_new_from_cuts(pdim, phase_cuts, &phase_global); + struct gkyl_comm *comm_conf = comm_new(conf_decomp); + struct gkyl_comm *comm_phase = comm_new(phase_decomp); + + const struct gkyl_range *conf_local = &conf_decomp->ranges[rank]; + const struct gkyl_range *phase_local = &phase_decomp->ranges[rank]; + + struct gkyl_basis basis_conf, basis_phase; + gkyl_cart_modal_serendip(&basis_conf, cdim, 1); + gkyl_cart_modal_gkhybrid(&basis_phase, cdim, vdim); + + struct lc_ctx ctx = { + .z_max = z_max, + .b_m = 4.0, + .r_m = 8.0, + .t0 = 100.0 * GKYL_ELEMENTARY_CHARGE, + .eV = GKYL_ELEMENTARY_CHARGE, + .phi_fac = 5.0, + }; + + struct gkyl_array *bmag_local = mkarr(basis_conf.num_basis, conf_local->volume); + struct gkyl_array *phi_local = mkarr(basis_conf.num_basis, conf_local->volume); + struct gkyl_array *bmag_global_gather = mkarr(basis_conf.num_basis, conf_global.volume); + struct gkyl_array *phi_global_gather = mkarr(basis_conf.num_basis, conf_global.volume); + + eval_dg_on_range(&grid_conf, &basis_conf, conf_local, bmag_func_1x, &ctx, bmag_local); + eval_dg_on_range(&grid_conf, &basis_conf, conf_local, phi_func_1x, &ctx, phi_local); + + gkyl_comm_array_allgather(comm_conf, conf_local, &conf_global, bmag_local, bmag_global_gather); + gkyl_comm_array_allgather(comm_conf, conf_local, &conf_global, phi_local, phi_global_gather); + + struct gkyl_mapc2p_inp c2p_in = { }; + struct gkyl_velocity_map *gvm_local = gkyl_velocity_map_new(c2p_in, grid_phase, grid_vel, + *phase_local, *phase_local, vel_global, vel_global, false); + + struct gkyl_loss_cone_mask_gyrokinetic *up_local = + gkyl_loss_cone_mask_gyrokinetic_inew(&(struct gkyl_loss_cone_mask_gyrokinetic_inp) { + .conf_basis = &basis_conf, + .vel_map = gvm_local, + .mass = 2.014 * GKYL_PROTON_MASS, + .charge = GKYL_ELEMENTARY_CHARGE, + }); + + struct gkyl_array *mask_local = mkarr(1, phase_local->volume); + gkyl_loss_cone_mask_gyrokinetic_advance(up_local, phase_local, &conf_global, + bmag_global_gather, phi_global_gather, 0, 0, mask_local); + + struct gkyl_array *mask_dist_global = mkarr(1, phase_global.volume); + gkyl_comm_array_allgather(comm_phase, phase_local, &phase_global, mask_local, mask_dist_global); + + if (rank == 0) { + struct gkyl_array *bmag_ref = mkarr(basis_conf.num_basis, conf_global.volume); + struct gkyl_array *phi_ref = mkarr(basis_conf.num_basis, conf_global.volume); + eval_dg_on_range(&grid_conf, &basis_conf, &conf_global, bmag_func_1x, &ctx, bmag_ref); + eval_dg_on_range(&grid_conf, &basis_conf, &conf_global, phi_func_1x, &ctx, phi_ref); + + struct gkyl_velocity_map *gvm_global = gkyl_velocity_map_new(c2p_in, grid_phase, grid_vel, + phase_global, phase_global, vel_global, vel_global, false); + struct gkyl_loss_cone_mask_gyrokinetic *up_ref = + gkyl_loss_cone_mask_gyrokinetic_inew(&(struct gkyl_loss_cone_mask_gyrokinetic_inp) { + .conf_basis = &basis_conf, + .vel_map = gvm_global, + .mass = 2.014 * GKYL_PROTON_MASS, + .charge = GKYL_ELEMENTARY_CHARGE, + }); + + struct gkyl_array *mask_ref = mkarr(1, phase_global.volume); + gkyl_loss_cone_mask_gyrokinetic_advance(up_ref, &phase_global, &conf_global, + bmag_ref, phi_ref, 0, 0, mask_ref); + + struct gkyl_range_iter it; + gkyl_range_iter_init(&it, &phase_global); + while (gkyl_range_iter_next(&it)) { + long linidx = gkyl_range_idx(&phase_global, it.idx); + const double *md = gkyl_array_cfetch(mask_dist_global, linidx); + const double *mr = gkyl_array_cfetch(mask_ref, linidx); + TEST_CHECK(fabs(md[0] - mr[0]) < 1e-12); + } + + gkyl_array_release(mask_ref); + gkyl_loss_cone_mask_gyrokinetic_release(up_ref); + gkyl_velocity_map_release(gvm_global); + gkyl_array_release(phi_ref); + gkyl_array_release(bmag_ref); + } + + gkyl_array_release(mask_dist_global); + gkyl_array_release(mask_local); + gkyl_loss_cone_mask_gyrokinetic_release(up_local); + gkyl_velocity_map_release(gvm_local); + gkyl_array_release(phi_global_gather); + gkyl_array_release(bmag_global_gather); + gkyl_array_release(phi_local); + gkyl_array_release(bmag_local); + gkyl_comm_release(comm_phase); + gkyl_comm_release(comm_conf); + gkyl_rect_decomp_release(phase_decomp); + gkyl_rect_decomp_release(conf_decomp); +} + +TEST_LIST = { + { "loss_cone_mask_parallel_4dom", test_loss_cone_mask_parallel_4dom }, + { NULL, NULL }, +}; diff --git a/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic.h b/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic.h index 36e12300c3..b2be718caf 100644 --- a/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic.h +++ b/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic.h @@ -2,78 +2,80 @@ #include #include -#include #include -#include #include // Object type. typedef struct gkyl_loss_cone_mask_gyrokinetic gkyl_loss_cone_mask_gyrokinetic; -// Type of function expected for the ctp_pos_func input. -typedef void (*loss_cone_mask_gyrokinetic_c2p_t)(const double *xcomp, double *xphys, void *ctx); - -// Available options: -// A) num_quad=1, qtype=GKYL_GAUSS_QUAD. Output: ncomp=1 array. -// B) num_quad>1, qtype=GKYL_GAUSS_QUAD or GKYL_GAUSS_LOBATTO_QUAD, cellwise_trap_loss=true. Output: ncomp=1 array. -// C) num_quad>1, qtype=GKYL_GAUSS_QUAD or GKYL_GAUSS_LOBATTO_QUAD, cellwise_trap_loss=false. Output: ncomp=phase_basis.ncomp array. +/** Guiding-center orbit behavior in one parallel direction. */ +enum gkyl_gk_trapped_passing_orbit_type { + GKYL_GK_TRAP_PASS_ORBIT_PASSING = 0, // The orbit reaches an absorbing plasma edge. + GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH, // The sheath potential may reflect the particle. + GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL, // An impenetrable wall reflects the particle. +}; // Inputs packaged as a struct. struct gkyl_loss_cone_mask_gyrokinetic_inp { - const struct gkyl_rect_grid *phase_grid; // Phase-space grid on which to compute moments. const struct gkyl_basis *conf_basis; // Configuration-space basis functions. - const struct gkyl_basis *phase_basis; // Phase-space basis functions. - const struct gkyl_range *conf_range; // Configuration-space range. - const struct gkyl_range *conf_range_ext; // Extended configuration-space range (for internal memory allocations). - const struct gkyl_range *vel_range; // Velocity space range. - const struct gkyl_velocity_map *vel_map; // Velocity space mapping object. - const struct gkyl_array *bmag; // Magnetic field magnitude. - const double *bmag_max; // Maximum bmag (on GPU if use_gpu=true). - const double *bmag_max_loc; // Location of maximum bmag (on GPU if use_gpu=true).. + const struct gkyl_velocity_map *vel_map; // Velocity-space mapping object. + bool use_gpu; // Flag to indicate if GPU should be used. double mass; // Species mass. double charge; // Species charge. - enum gkyl_quad_type qtype; // Quadrature rule/nodes. - int num_quad; // Number of quad points in each direction to use (default: poly_order+1). - bool cellwise_trap_loss; // =True takes a whole cell to be either trapped or passing, - // so not high-order distinction within the cell is made. - loss_cone_mask_gyrokinetic_c2p_t c2p_pos_func; // Function that transforms a set of cdim - // position-space computational coordinates to physical ones. - void *c2p_pos_func_ctx; // Context for c2p_pos_func. - bool use_gpu; // Whether to run on GPU. + enum gkyl_gk_trapped_passing_orbit_type lower_orbit; // Lower-z orbit type. + enum gkyl_gk_trapped_passing_orbit_type upper_orbit; // Upper-z orbit type. }; /** * Create new updater that populates an array with the masking function - * if (mu > mu_bound) - * f = 1 - * else - * f = 0 - * where mu_bound = (0.5*m*vpar^2+q*(phi-phi_m))/(B*(B_max/B-1)) - * is the trapped-passing boundary in vpar-mu space. + * based on the escape-barrier criterion. Let + * + * H = (1/2) m v_parallel^2 + U(z,mu), U(z,mu) = mu B(z) + q phi(z). + * + * For a passing orbit, the directional escape barrier is the largest U + * between the particle and its absorbing plasma edge. An orbit subject to + * sheath trapping also includes the material-wall endpoint + * + * U_wall = mu B(z_edge) + q phi_wall. + * + * A wall-trapped orbit has an infinite escape barrier. A phase-space corner + * is trapped only when H is below both directional barriers; a cell is masked + * as trapped only when all of its corners are trapped. * * @param inp Input parameters defined in gkyl_loss_cone_mask_gyrokinetic_inp struct. * @return New updater pointer. */ -struct gkyl_loss_cone_mask_gyrokinetic* +struct gkyl_loss_cone_mask_gyrokinetic* gkyl_loss_cone_mask_gyrokinetic_inew(const struct gkyl_loss_cone_mask_gyrokinetic_inp *inp); /** - * Compute projection of the loss cone masking function on the phase-space basis. + * Compute the loss-cone mask from phase-space cell corners. + * + * The caller supplies the magnetic field magnitude and electrostatic + * potential arrays. This keeps the updater free of any communication logic; + * the app is responsible for assembling global data when needed. * - * @param up Project on basis updater to run. + * @param up Loss-cone mask updater to run. * @param phase_rng Phase-space range. * @param conf_rng Configuration-space range. - * @param phi Electrostatic potential. - * @param phi_m Electrostatic potential at the mirror throat (on GPU if use_gpu=true). - * @param mask_out Output masking function. + * @param bmag Magnetic field magnitude (cdim DG expansion). + * @param phi Electrostatic potential throughout the configuration-space range. + * @param phi_wall_lo Lower material-wall potential. Required for a lower + * sheath-trapped orbit; ignored otherwise. + * @param phi_wall_up Upper material-wall potential. Required for an upper + * sheath-trapped orbit; ignored otherwise. + * @param mask_out Cellwise mask: 1 if every cell corner is trapped, 0 if any + * corner can escape through either boundary. */ void gkyl_loss_cone_mask_gyrokinetic_advance(gkyl_loss_cone_mask_gyrokinetic *up, const struct gkyl_range *phase_range, const struct gkyl_range *conf_range, - const struct gkyl_array *phi, const double *phi_m, struct gkyl_array *mask_out); + const struct gkyl_array *bmag, const struct gkyl_array *phi, + const struct gkyl_array *phi_wall_lo, const struct gkyl_array *phi_wall_up, + struct gkyl_array *mask_out); /** * Delete updater. * * @param up Updater to delete. */ -void gkyl_loss_cone_mask_gyrokinetic_release(gkyl_loss_cone_mask_gyrokinetic* up); +void gkyl_loss_cone_mask_gyrokinetic_release(gkyl_loss_cone_mask_gyrokinetic *up); diff --git a/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic_priv.h b/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic_priv.h index 03e57dec97..ae862d493c 100644 --- a/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic_priv.h +++ b/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic_priv.h @@ -1,112 +1,200 @@ // Private header: not for direct use #pragma once +#include #include #include -#include -#include -#include +#include #include -#include #include -#include - -GKYL_CU_DH -static inline void -log_to_comp(int ndim, const double *eta, - const double * GKYL_RESTRICT dx, const double * GKYL_RESTRICT xc, - double* GKYL_RESTRICT xout) -{ - for (int d=0; dDbmag_quad. - * - * @param up Project on basis updater to run. - * @param conf_rng Configuration-space range. - * @param bmag Magnetic field magnitude. - * @param bmag_max Maximum bmag. + * Return the P1 corner with the anchor's transverse coordinates and the + * requested lower (0) or upper (1) side in zdim. */ -void -gkyl_loss_cone_mask_gyrokinetic_Dbmag_quad_cu(gkyl_loss_cone_mask_gyrokinetic *up, - const struct gkyl_range *conf_range, const struct gkyl_array *bmag, const double *bmag_max); +GKYL_CU_DH +static inline int +gk_lcm_conf_corner_with_z_side(const struct gkyl_range *conf_corner_range, + int anchor_corner, int zdim, int z_side) +{ + int corner_idx[GKYL_MAX_CDIM] = { 0 }; + gkyl_range_inv_idx(conf_corner_range, anchor_corner, corner_idx); + corner_idx[zdim] = z_side; + return gkyl_range_idx(conf_corner_range, corner_idx); +} + +/** Evaluate a configuration-space DG field at a range-linear P1 cell corner. */ +GKYL_CU_DH +static inline double +gk_lcm_field_corner_value(const struct gkyl_array *arr, + const struct gkyl_array *basis_at_corners, int num_basis, long linidx, + int corner) +{ + const double *arr_d = (const double *)gkyl_array_cfetch(arr, linidx); + const double *basis_d = (const double *)gkyl_array_cfetch(basis_at_corners, corner); + + double val = 0.0; + for (int k = 0; k < num_basis; ++k) { + val += arr_d[k] * basis_d[k]; + } + + return val; +} + +/** + * Return true when the particle Hamiltonian is strictly below one directional + * escape barrier. Equality means that the particle reaches the loss endpoint + * with zero parallel kinetic energy. + */ +GKYL_CU_DH +static inline bool +gk_lcm_hamiltonian_below_barrier(double kinetic_energy, double magnetic_energy, + double electric_energy, double barrier) +{ + if (barrier == DBL_MAX) { + return true; + } + + double hamiltonian = kinetic_energy + magnetic_energy + electric_energy; + double energy_scale = fabs(kinetic_energy) + fabs(magnetic_energy) + + fabs(electric_energy) + fabs(barrier); + double roundoff_tol = 32.0 * DBL_EPSILON * energy_scale; + + // Treat equality, including equality to within modal-evaluation roundoff, + // as reaching the boundary rather than reflecting from it. + return hamiltonian < barrier - roundoff_tol; +} /** - * Compute projection of the loss cone masking function on the phase-space basis - * on the GPU. - * - * @param up Project on basis updater to run. - * @param phase_rng Phase-space range. - * @param conf_rng Configuration-space range. - * @param phi Electrostatic potential. - * @param phi_m Electrostatic potential at the mirror throat (on GPU). - * @param mask_out Output masking function. + * Compute the maximum effective potential U=mu*B+q*phi along the lower- and + * upper-z orbits from one configuration-space P1 corner. base_idx fixes the + * transverse cell indices, z_cell is the anchor cell, and a sheath-trapped + * orbit includes the material-wall potential. */ -void -gkyl_loss_cone_mask_gyrokinetic_advance_cu(gkyl_loss_cone_mask_gyrokinetic *up, +GKYL_CU_DH +static inline void +gk_lcm_escape_barriers(int cdim, int num_basis_conf, + const struct gkyl_range *conf_range, const struct gkyl_range *conf_corner_range, + const struct gkyl_array *basis_at_corners_conf, const struct gkyl_array *phi, + const struct gkyl_array *bmag, const struct gkyl_array *phi_wall_lo, + const struct gkyl_array *phi_wall_up, const int *base_idx, int z_cell, + int anchor_conf_corner, double mu, double charge, + enum gkyl_gk_trapped_passing_orbit_type lower_orbit, + enum gkyl_gk_trapped_passing_orbit_type upper_orbit, + double *barrier_left, double *barrier_right) +{ + int zdim = cdim - 1; + + int z_side = gk_lcm_conf_corner_z_side(conf_corner_range, anchor_conf_corner, zdim); + int z_upper_corner = gk_lcm_conf_corner_with_z_side(conf_corner_range, + anchor_conf_corner, zdim, 1); + int z_lower_corner = gk_lcm_conf_corner_with_z_side(conf_corner_range, + anchor_conf_corner, zdim, 0); + + int scan_idx[GKYL_MAX_CDIM]; + for (int d = 0; d < cdim; ++d) { + scan_idx[d] = base_idx[d]; + } + + *barrier_left = -DBL_MAX; + *barrier_right = -DBL_MAX; + + for (int iz = conf_range->lower[zdim]; iz <= conf_range->upper[zdim]; ++iz) { + scan_idx[zdim] = iz; + long linidx = gkyl_range_idx(conf_range, scan_idx); + + double phi_lower = gk_lcm_field_corner_value(phi, basis_at_corners_conf, + num_basis_conf, linidx, z_lower_corner); + double phi_upper = gk_lcm_field_corner_value(phi, basis_at_corners_conf, + num_basis_conf, linidx, z_upper_corner); + double bmag_lower = gk_lcm_field_corner_value(bmag, basis_at_corners_conf, + num_basis_conf, linidx, z_lower_corner); + double bmag_upper = gk_lcm_field_corner_value(bmag, basis_at_corners_conf, + num_basis_conf, linidx, z_upper_corner); + double u_lower = mu * bmag_lower + charge * phi_lower; + double u_upper = mu * bmag_upper + charge * phi_upper; + + // A complete cell contributes both endpoint traces. In the anchor cell, + // only the portion between the anchor corner and the relevant wall belongs + // to that directional path. Keeping both traces also handles discontinuous + // input fields conservatively. + if (iz < z_cell) { + *barrier_left = GKYL_MAX2(*barrier_left, GKYL_MAX2(u_lower, u_upper)); + } + else if (iz == z_cell) { + *barrier_left = GKYL_MAX2(*barrier_left, u_lower); + *barrier_right = GKYL_MAX2(*barrier_right, u_upper); + if (z_side == 1) { + *barrier_left = GKYL_MAX2(*barrier_left, u_upper); + } + else { + *barrier_right = GKYL_MAX2(*barrier_right, u_lower); + } + } + else { + *barrier_right = GKYL_MAX2(*barrier_right, GKYL_MAX2(u_lower, u_upper)); + } + } + + if (lower_orbit == GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL) { + *barrier_left = DBL_MAX; + } + else if (lower_orbit == GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH) { + scan_idx[zdim] = conf_range->lower[zdim]; + long linidx = gkyl_range_idx(conf_range, scan_idx); + double bmag_wall = gk_lcm_field_corner_value(bmag, basis_at_corners_conf, + num_basis_conf, linidx, z_lower_corner); + double phi_wall = gk_lcm_field_corner_value(phi_wall_lo, basis_at_corners_conf, + num_basis_conf, linidx, z_lower_corner); + *barrier_left = GKYL_MAX2(*barrier_left, mu * bmag_wall + charge * phi_wall); + } + + if (upper_orbit == GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL) { + *barrier_right = DBL_MAX; + } + else if (upper_orbit == GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH) { + scan_idx[zdim] = conf_range->upper[zdim]; + long linidx = gkyl_range_idx(conf_range, scan_idx); + double bmag_wall = gk_lcm_field_corner_value(bmag, basis_at_corners_conf, + num_basis_conf, linidx, z_upper_corner); + double phi_wall = gk_lcm_field_corner_value(phi_wall_up, basis_at_corners_conf, + num_basis_conf, linidx, z_upper_corner); + *barrier_right = GKYL_MAX2(*barrier_right, mu * bmag_wall + charge * phi_wall); + } +} + +#ifdef GKYL_HAVE_CUDA +void gkyl_loss_cone_mask_gyrokinetic_advance_cu(gkyl_loss_cone_mask_gyrokinetic *up, const struct gkyl_range *phase_range, const struct gkyl_range *conf_range, - const struct gkyl_array *phi, const double *phi_m, struct gkyl_array *mask_out); + const struct gkyl_array *bmag, const struct gkyl_array *phi, + const struct gkyl_array *phi_wall_lo, const struct gkyl_array *phi_wall_up, + struct gkyl_array *mask_out); #endif diff --git a/gyrokinetic/zero/gkyl_position_map_priv.h b/gyrokinetic/zero/gkyl_position_map_priv.h index ab3b3ae3c5..04b8fb18aa 100644 --- a/gyrokinetic/zero/gkyl_position_map_priv.h +++ b/gyrokinetic/zero/gkyl_position_map_priv.h @@ -259,8 +259,7 @@ calculate_optimal_mapping_polynomial(struct gkyl_position_map_const_B_ctx *const // Utility functions for numeric root finding B mapping /** - * Calculates dB/dTheta numerically at a given xn value. Calculates - * the derivative to the left of the point. + * Calculates dB/dTheta at theta with a second-order centered difference. * * @param theta The theta value to calculate the derivative at * @param ctx The context for the position map @@ -276,13 +275,24 @@ calc_bmag_global_derivative(double theta, void *ctx) double fout[3]; xh[0] = gpm->constB_ctx->psi; xh[1] = gpm->constB_ctx->alpha; - xh[2] = theta - h; + xh[2] = theta + h; gkyl_calc_bmag_global(0.0, xh, fout, bmag_ctx); double Bmag_plus = fout[0]; - xh[2] = theta - 2*h; + xh[2] = theta - h; gkyl_calc_bmag_global(0.0, xh, fout, bmag_ctx); double Bmag_minus = fout[0]; - return (Bmag_plus - Bmag_minus) / (h); + return (Bmag_plus - Bmag_minus) / (2*h); +} + +/** + * Return true when dB/dtheta is small relative to the local field variation + * scale B/dtheta_sample. This dimensionless comparison remains meaningful if + * the magnetic-field units or normalization change. + */ +static bool +bmag_derivative_is_small(double dbmag, double bmag, double dtheta_sample) +{ + return fabs(dbmag) * dtheta_sample < 1e-10 * fabs(bmag); } /** @@ -314,15 +324,30 @@ find_B_field_extrema(struct gkyl_position_map *gpm) double *theta_extrema = gkyl_malloc(sizeof(double) * (npts + 1)); double *bmag_extrema = gkyl_malloc(sizeof(double) * (npts + 1)); - for (int i = 0; i <= npts; i++){ + // Seed the previous sample used by the first sign-change comparison. + xp[Z_IDX] = theta_lo; + gkyl_calc_bmag_global(0.0, xp, &bmag_vals[0], bmag_ctx); + dbmag_vals[0] = calc_bmag_global_derivative(theta_lo, gpm); + + for (int i = 1; i < npts; i++){ double theta = theta_lo + i * theta_dxi; xp[Z_IDX] = theta; gkyl_calc_bmag_global(0.0, xp, &bmag_vals[i], bmag_ctx); dbmag_vals[i] = calc_bmag_global_derivative(theta, gpm); - if (i==0) continue; - // Minima - if (dbmag_vals[i] > 0 && dbmag_vals[i-1] < 0){ + // A near-stationary sample is an extrema candidate. Classification as a + // minimum or maximum occurs below after all candidates are collected. + if (bmag_derivative_is_small(dbmag_vals[i], bmag_vals[i], theta_dxi)) { + theta_extrema[extrema] = theta; + bmag_extrema[extrema] = bmag_vals[i]; + extrema++; + continue; + } + + // Minima via sign change. Guard on |dbmag[i-1]| to avoid a double-record if the + // previous point was already captured by the near-zero branch above. + if (dbmag_vals[i] > 0 && dbmag_vals[i-1] < 0 && + !bmag_derivative_is_small(dbmag_vals[i-1], bmag_vals[i-1], theta_dxi)){ if (bmag_vals[i] < bmag_vals[i-1]) { theta_extrema[extrema] = theta; @@ -337,8 +362,9 @@ find_B_field_extrema(struct gkyl_position_map *gpm) } } - // Maxima - if (dbmag_vals[i] < 0 && dbmag_vals[i-1] > 0){ + // Maxima via sign change. Guard on |dbmag[i-1]| for the same reason. + if (dbmag_vals[i] < 0 && dbmag_vals[i-1] > 0 && + !bmag_derivative_is_small(dbmag_vals[i-1], bmag_vals[i-1], theta_dxi)){ if (bmag_vals[i] > bmag_vals[i-1]) { theta_extrema[extrema] = theta; @@ -397,7 +423,7 @@ find_B_field_extrema(struct gkyl_position_map *gpm) { gpm->constB_ctx->min_or_max[extrema-1] = 1; } // Maximum else if (bmag_extrema[extrema-1] < bmag_extrema[extrema-2]) { gpm->constB_ctx->min_or_max[extrema-1] = 0; } // Minimum - else + else { printf("Error: Extrema is not an extrema. Position_map optimization failed\n"); } // Free mallocs @@ -672,8 +698,11 @@ position_map_constB_z_numeric(double t, const double *xn, double *fout, void *ct if (enable_limits_min_B || enable_limits_max_B) { - // Set a minimum cell size on the edges - // Assume that at inflection points, Theta = theta. This should be true + // Set a minimum cell size on the edges. + // Build limiter lines from inflection-point anchors in point-slope form: + // y = y0 + m * (x - x0) + // where x0 is the computational-coordinate location of a region boundary + // and y0 is the mapped extrema location in physical theta. double Theta_left = interval_lower; double Theta_right = interval_upper; double theta_middle = 0.5 * (interval_lower + interval_upper); @@ -693,20 +722,19 @@ position_map_constB_z_numeric(double t, const double *xn, double *fout, void *ct double max_slope_min_B = gpm->constB_ctx->maximum_slope_at_min_B; double max_slope_max_B = gpm->constB_ctx->maximum_slope_at_max_B; - double right_straight_line_value, left_straight_line_value; - if (left_is_maximum){ - left_straight_line_value = max_slope_max_B * theta + (1-max_slope_max_B) * Theta_left; - } - else { - left_straight_line_value = max_slope_min_B * theta + (1-max_slope_min_B) * Theta_left; - } + // Compute x-locations (in computational theta) of left/right inflection anchors. + double seg_dB = fabs(gpm->constB_ctx->bmag_extrema[region+1] - gpm->constB_ctx->bmag_extrema[region]); + double it_left_inflection = dB_global_lower / dB_cell; + double it_right_inflection = (dB_global_lower + seg_dB) / dB_cell; + double theta_left_inflection = theta_lo + it_left_inflection * theta_dxi; + double theta_right_inflection = theta_lo + it_right_inflection * theta_dxi; - if (right_is_maximum){ - right_straight_line_value = max_slope_max_B * theta + (1-max_slope_max_B) * Theta_right; - } - else { - right_straight_line_value = max_slope_min_B * theta + (1-max_slope_min_B) * Theta_right; - } + // Build limiter lines in point-slope form so they pass through + // (theta_left_inflection, Theta_left) and (theta_right_inflection, Theta_right). + double left_slope = left_is_maximum ? max_slope_max_B : max_slope_min_B; + double right_slope = right_is_maximum ? max_slope_max_B : max_slope_min_B; + double left_straight_line_value = Theta_left + left_slope * (theta - theta_left_inflection); + double right_straight_line_value = Theta_right + right_slope * (theta - theta_right_inflection); if ( fout[0] < right_straight_line_value && ((right_is_maximum && enable_limits_max_B) || @@ -909,4 +937,3 @@ position_map_deriv_sep_compression(double t, const double *xn, double *fout, voi double deriv = A * (-cos(M_PI*xshift/w) + F); fout[0] = deriv; } - diff --git a/gyrokinetic/zero/loss_cone_mask_gyrokinetic.c b/gyrokinetic/zero/loss_cone_mask_gyrokinetic.c index 8eee0f3ef6..5a6ca1b45c 100644 --- a/gyrokinetic/zero/loss_cone_mask_gyrokinetic.c +++ b/gyrokinetic/zero/loss_cone_mask_gyrokinetic.c @@ -1,485 +1,183 @@ -#include -#include +#include #include #include -#include -#include -#include -#include #include #include #include -#include -// -// mu_bound = (0.5*mass*pow(vpar,2)+charge*Delta_phi)/(bmag[0]*(Rm-1)); -// = 0.5*mass*pow(vpar,2)/(bmag[0]*(Rm-1)) + charge*Delta_phi/(bmag[0]*(Rm-1)); -// = 0.5*mass*pow(vpar,2)/(bmag_max-bmag[0]) + charge*(phi-phi_m)/(bmag_max-bmag[0]); -// +// The loss-cone mask is built from the lower- and upper-z escape barriers. +// A sheath-trapped orbit includes the material-wall potential as an endpoint. -// create range to loop over quadrature points. -static inline struct gkyl_range -get_qrange(int cdim, int dim, int num_quad, int num_quad_v, bool *is_vdim_p2) +/** Allocate host or device array storage for precomputed corner data. */ +static struct gkyl_array* +gk_lcm_mkarr(long nc, long size, bool use_gpu) { - int qshape[GKYL_MAX_DIM]; - for (int i=0; indim; - int num_quad_v = num_quad; - // Hybrid basis have p=2 in velocity space. - bool is_vdim_p2[2] = {false}; // 2 is the max vdim for GK. - if (num_quad > 1 && basis->b_type == GKYL_BASIS_MODAL_GKHYBRID) { - num_quad_v = num_quad+1; - is_vdim_p2[0] = true; // only vpar is quadratic in GK hybrid. - } - - double ordinates1[num_quad], weights1[num_quad]; - double ordinates1_v[num_quad_v], weights1_v[num_quad_v]; - - if (qtype == GKYL_GAUSS_QUAD) { - if (num_quad <= gkyl_gauss_max) { - // Use pre-computed values if possible (these are more accurate than computing them on the fly). - memcpy(ordinates1, gkyl_gauss_ordinates[num_quad], sizeof(double[num_quad])); - memcpy(weights1, gkyl_gauss_weights[num_quad], sizeof(double[num_quad])); - } - else { - gkyl_gauleg(-1, 1, ordinates1, weights1, num_quad); - } - if (num_quad_v <= gkyl_gauss_max) { - memcpy(ordinates1_v, gkyl_gauss_ordinates[num_quad_v], sizeof(double[num_quad_v])); - memcpy(weights1_v, gkyl_gauss_weights[num_quad_v], sizeof(double[num_quad_v])); - } - else { - gkyl_gauleg(-1, 1, ordinates1_v, weights1_v, num_quad_v); - } - } - else if (qtype == GKYL_GAUSS_LOBATTO_QUAD) { - assert( (num_quad > 1) && (num_quad <= gkyl_gauss_max) ); - // Gauss-Lobatto quadrature - memcpy(ordinates1, gkyl_gauss_lobatto_ordinates[num_quad], sizeof(double[num_quad])); - memcpy(weights1, gkyl_gauss_lobatto_weights[num_quad], sizeof(double[num_quad])); - - assert( (num_quad_v > 1) && (num_quad_v <= gkyl_gauss_max) ); - memcpy(ordinates1_v, gkyl_gauss_lobatto_ordinates[num_quad_v], sizeof(double[num_quad_v])); - memcpy(weights1_v, gkyl_gauss_lobatto_weights[num_quad_v], sizeof(double[num_quad_v])); - } - else { - fprintf(stderr, "Quadrature rule not available.\n"); - assert(false); - } - - struct gkyl_range qrange = get_qrange(cdim, ndim, num_quad, num_quad_v, is_vdim_p2); - - int tot_quad = qrange.volume; - - // create ordinates and weights for multi-D quadrature - struct gkyl_array *ordinates_ho = gkyl_array_new(GKYL_DOUBLE, ndim, tot_quad); - struct gkyl_array *weights_ho = gkyl_array_new(GKYL_DOUBLE, 1, tot_quad); - if (use_gpu) { - *ordinates = gkyl_array_cu_dev_new(GKYL_DOUBLE, ndim, tot_quad); - *weights = gkyl_array_cu_dev_new(GKYL_DOUBLE, 1, tot_quad); - } - else { - *ordinates = gkyl_array_new(GKYL_DOUBLE, ndim, tot_quad); - *weights = gkyl_array_new(GKYL_DOUBLE, 1, tot_quad); - } - - struct gkyl_range_iter iter; - gkyl_range_iter_init(&iter, &qrange); - - while (gkyl_range_iter_next(&iter)) { - int node = gkyl_range_idx(&qrange, iter.idx); - - // set ordinates - double *ord = gkyl_array_fetch(ordinates_ho, node); - for (int i=0; inum_basis, tot_quad); - if (use_gpu) - *basis_at_ords = gkyl_array_cu_dev_new(GKYL_DOUBLE, basis->num_basis, tot_quad); - else - *basis_at_ords = gkyl_array_new(GKYL_DOUBLE, basis->num_basis, tot_quad); - - for (int n=0; neval(gkyl_array_fetch(ordinates_ho, n), gkyl_array_fetch(basis_at_ords_ho, n)); - - // Copy host array to device array. - gkyl_array_copy(*ordinates, ordinates_ho); - gkyl_array_copy(*weights, weights_ho); - gkyl_array_copy(*basis_at_ords, basis_at_ords_ho); - - gkyl_array_release(ordinates_ho); - gkyl_array_release(weights_ho); - gkyl_array_release(basis_at_ords_ho); - - return tot_quad; + return use_gpu? gkyl_array_cu_dev_new(GKYL_DOUBLE, nc, size) + : gkyl_array_new(GKYL_DOUBLE, nc, size); } +/** + * Precompute the configuration-space basis at every tensor-product P1 cell + * corner. These are interpolation/cell-classification corners, not Gaussian + * quadrature, positivity, or modal-basis nodes. + */ static void -gkyl_loss_cone_mask_gyrokinetic_Dbmag_quad(gkyl_loss_cone_mask_gyrokinetic *up, - const struct gkyl_range *conf_range, const struct gkyl_array *bmag, const double *bmag_max) +gk_lcm_init_corner_values(const struct gkyl_range *corner_range, + const struct gkyl_basis *basis, struct gkyl_array **basis_at_corners, + bool use_gpu) { - // Get bmag_max-bmag at quadrature nodes. -#ifdef GKYL_HAVE_CUDA - if (up->use_gpu) - return gkyl_loss_cone_mask_gyrokinetic_Dbmag_quad_cu(up, conf_range, bmag, bmag_max); -#endif - - int cdim = up->cdim, pdim = up->pdim; - - int tot_quad_conf = up->tot_quad_conf; - int num_basis_conf = up->num_basis_conf; - - struct gkyl_range_iter conf_iter; - gkyl_range_iter_init(&conf_iter, conf_range); - while (gkyl_range_iter_next(&conf_iter)) { - long linidx = gkyl_range_idx(conf_range, conf_iter.idx); - - const double *bmag_d = gkyl_array_cfetch(bmag, linidx); - double *Dbmag_quad = gkyl_array_fetch(up->Dbmag_quad, linidx); - - // Sum over basis - for (int n=0; nbasis_at_ords_conf, n); - for (int k=0; knum_basis, corner_range->volume); + *basis_at_corners = gk_lcm_mkarr(basis->num_basis, corner_range->volume, use_gpu); + + double eta[GKYL_MAX_DIM] = { 0.0 }; + struct gkyl_range_iter corner_iter; + gkyl_range_iter_init(&corner_iter, corner_range); + while (gkyl_range_iter_next(&corner_iter)) { + for (int d = 0; d < corner_range->ndim; ++d) { + eta[d] = corner_iter.idx[d] ? 1.0 : -1.0; } + long corner = gkyl_range_idx(corner_range, corner_iter.idx); + basis->eval(eta, gkyl_array_fetch(basis_at_corners_ho, corner)); } + + gkyl_array_copy(*basis_at_corners, basis_at_corners_ho); + gkyl_array_release(basis_at_corners_ho); } -struct gkyl_loss_cone_mask_gyrokinetic* +struct gkyl_loss_cone_mask_gyrokinetic* gkyl_loss_cone_mask_gyrokinetic_inew(const struct gkyl_loss_cone_mask_gyrokinetic_inp *inp) { gkyl_loss_cone_mask_gyrokinetic *up = gkyl_malloc(sizeof(*up)); - up->grid_phase = inp->phase_grid; up->vel_map = gkyl_velocity_map_acquire(inp->vel_map); up->mass = inp->mass; up->charge = inp->charge; - - up->cdim = inp->conf_basis->ndim; - up->pdim = inp->phase_basis->ndim; - - up->cellwise_trap_loss = inp->cellwise_trap_loss; - int num_quad = inp->num_quad? inp->num_quad : inp->phase_basis->poly_order+1; - up->norm_fac = 1; - if (!up->cellwise_trap_loss) - up->norm_fac = num_quad == 1? 1.0/pow(sqrt(2.0),up->pdim) : 1.0; - - if (num_quad == 1) { - up->num_basis_conf = 1; - up->num_basis_phase = 1; - } - else { - up->num_basis_conf = inp->conf_basis->num_basis; - up->num_basis_phase = inp->phase_basis->num_basis; - } up->use_gpu = inp->use_gpu; + up->lower_orbit = inp->lower_orbit; + up->upper_orbit = inp->upper_orbit; - // Initialize data needed for conf-space quadrature. - up->tot_quad_conf = init_quad_values(up->cdim, inp->conf_basis, inp->qtype, num_quad, - &up->ordinates_conf, &up->weights_conf, &up->basis_at_ords_conf, false); - - // Initialize data needed for phase-space quadrature. - up->tot_quad_phase = init_quad_values(up->cdim, inp->phase_basis, inp->qtype, num_quad, - &up->ordinates_phase, &up->weights_phase, &up->basis_at_ords_phase, false); - - up->fun_at_ords = gkyl_array_new(GKYL_DOUBLE, 1, up->tot_quad_phase); // Only used in CPU implementation. - - // To avoid creating iterators over ranges in device kernel, we'll - // create a map between phase-space and conf-space ordinates. - int num_quad_v = num_quad; // Hybrid basis have p=2 in velocity space. - // hybrid basis have p=2 in velocity space. - bool is_vdim_p2[2] = {false}; // 2 is the max vdim for GK. - if (num_quad > 1 && inp->phase_basis->b_type == GKYL_BASIS_MODAL_GKHYBRID) { - num_quad_v = num_quad+1; - is_vdim_p2[0] = true; // only vpar is quadratic in GK hybrid. - } - up->conf_qrange = get_qrange(up->cdim, up->cdim, num_quad, num_quad_v, is_vdim_p2); - up->phase_qrange = get_qrange(up->cdim, up->pdim, num_quad, num_quad_v, is_vdim_p2); - - long conf_local_ncells = inp->conf_range->volume; - long conf_local_ext_ncells = inp->conf_range_ext->volume; - -#ifdef GKYL_HAVE_CUDA - if (up->use_gpu) { - // Allocate device copies of arrays needed for quadrature. - - int p2c_qidx_ho[up->phase_qrange.volume]; - up->p2c_qidx = (int*) gkyl_cu_malloc(sizeof(int)*up->phase_qrange.volume); - - // Allocate mask_quad at phase-space quadrature points. - // Dbmag_quad at configuration-space quadrature points. - // qDphiDbmag_quad, the term proportional to (phi-phi_m)/(bmag_max-bmag), at quadrature points. - up->mask_out_quad = gkyl_array_cu_dev_new(GKYL_DOUBLE, up->tot_quad_phase, - inp->conf_range_ext->volume*inp->vel_range->volume); - up->qDphiDbmag_quad = gkyl_array_cu_dev_new(GKYL_DOUBLE, up->tot_quad_conf, inp->conf_range_ext->volume); + assert(up->lower_orbit >= GKYL_GK_TRAP_PASS_ORBIT_PASSING && + up->lower_orbit <= GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL); + assert(up->upper_orbit >= GKYL_GK_TRAP_PASS_ORBIT_PASSING && + up->upper_orbit <= GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_WALL); - // Allocate the memory for computing the specific phase nodal to modal calculation - struct gkyl_mat_mm_array_mem *phase_nodal_to_modal_mem_ho; - phase_nodal_to_modal_mem_ho = gkyl_mat_mm_array_mem_new(up->num_basis_phase, up->tot_quad_phase, 1.0, 0.0, - GKYL_NO_TRANS, GKYL_NO_TRANS, false); - - // Compute the matrix A for the phase nodal to modal memory - const double *phase_w = (const double*) up->weights_phase->data; - const double *phaseb_o = (const double*) up->basis_at_ords_phase->data; - for (int n=0; ntot_quad_phase; ++n) { - for (int k=0; knum_basis_phase; ++k) - gkyl_mat_set(phase_nodal_to_modal_mem_ho->A, k, n, phase_w[n]*phaseb_o[k+up->num_basis_phase*n]); - } - - // Copy to device - up->phase_nodal_to_modal_mem = gkyl_mat_mm_array_mem_new(up->num_basis_phase, up->tot_quad_phase, 1.0, 0.0, - GKYL_NO_TRANS, GKYL_NO_TRANS, up->use_gpu); - gkyl_mat_copy(up->phase_nodal_to_modal_mem->A, phase_nodal_to_modal_mem_ho->A); - gkyl_mat_mm_array_mem_release(phase_nodal_to_modal_mem_ho); - - // Initialize data needed for conf-space quadrature on device. - up->tot_quad_conf = init_quad_values(up->cdim, inp->conf_basis, inp->qtype, num_quad, - &up->ordinates_conf, &up->weights_conf, &up->basis_at_ords_conf, up->use_gpu); - - // Initialize data needed for phase-space quadrature on device. - up->tot_quad_phase = init_quad_values(up->cdim, inp->phase_basis, inp->qtype, num_quad, - &up->ordinates_phase, &up->weights_phase, &up->basis_at_ords_phase, up->use_gpu); - - int pidx[GKYL_MAX_DIM]; - for (int n=0; ntot_quad_phase; ++n) { - gkyl_range_inv_idx(&up->phase_qrange, n, pidx); - int cqidx = gkyl_range_idx(&up->conf_qrange, pidx); - p2c_qidx_ho[n] = cqidx; - } - gkyl_cu_memcpy(up->p2c_qidx, p2c_qidx_ho, sizeof(int)*up->phase_qrange.volume, GKYL_CU_MEMCPY_H2D); + up->cdim = inp->conf_basis->ndim; + up->num_basis_conf = inp->conf_basis->num_basis; + int corner_shape[GKYL_MAX_CDIM]; + for (int d = 0; d < up->cdim; ++d) { + corner_shape[d] = 2; } -#endif - - // Allocate and obtain bmag_max-bmag at quadrature points. - if (up->use_gpu) - up->Dbmag_quad = gkyl_array_cu_dev_new(GKYL_DOUBLE, up->tot_quad_conf, inp->conf_range_ext->volume); - else - up->Dbmag_quad = gkyl_array_new(GKYL_DOUBLE, up->tot_quad_conf, inp->conf_range_ext->volume); + gkyl_range_init_from_shape(&up->conf_corner_range, up->cdim, corner_shape); - gkyl_array_clear(up->Dbmag_quad, 0.0); - gkyl_loss_cone_mask_gyrokinetic_Dbmag_quad(up, inp->conf_range, inp->bmag, inp->bmag_max); + gk_lcm_init_corner_values(&up->conf_corner_range, inp->conf_basis, + &up->basis_at_corners_conf, inp->use_gpu); - // Save the location of bmag_max in this updater. - if (up->use_gpu) { - up->bmag_max_loc = gkyl_cu_malloc(sizeof(double)*up->cdim); - gkyl_cu_memcpy(up->bmag_max_loc, inp->bmag_max_loc, sizeof(double)*up->cdim, GKYL_CU_MEMCPY_D2D); - } - else { - up->bmag_max_loc = gkyl_malloc(sizeof(double)*up->cdim); - memcpy(up->bmag_max_loc, inp->bmag_max_loc, sizeof(double)*up->cdim); - } - return up; } -static void -proj_on_basis(const gkyl_loss_cone_mask_gyrokinetic *up, const struct gkyl_array *fun_at_ords, double* f) -{ - int num_basis = up->num_basis_phase; - int tot_quad = up->tot_quad_phase; - - const double* GKYL_RESTRICT weights = up->weights_phase->data; - const double* GKYL_RESTRICT basis_at_ords = up->basis_at_ords_phase->data; - const double* GKYL_RESTRICT func_at_ords = fun_at_ords->data; - - for (int k=0; knum_basis_phase; - int tot_quad = up->tot_quad_phase; - - const double* GKYL_RESTRICT weights = up->weights_phase->data; - const double* GKYL_RESTRICT basis_at_ords = up->basis_at_ords_phase->data; - const double* GKYL_RESTRICT func_at_ords = fun_at_ords->data; - - for (int k=0; klower_orbit != GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH || phi_wall_lo); + assert(up->upper_orbit != GKYL_GK_TRAP_PASS_ORBIT_TRAPPED_SHEATH || phi_wall_up); #ifdef GKYL_HAVE_CUDA - if (up->use_gpu) - return gkyl_loss_cone_mask_gyrokinetic_advance_cu(up, phase_range, conf_range, - phi, phi_m, mask_out); + if (up->use_gpu) { + gkyl_loss_cone_mask_gyrokinetic_advance_cu(up, phase_range, conf_range, bmag, phi, + phi_wall_lo, phi_wall_up, mask_out); + return; + } #endif - int cdim = up->cdim, pdim = up->pdim; - int vdim = pdim-cdim; - - int tot_quad_conf = up->tot_quad_conf; + int cdim = up->cdim; + int pdim = phase_range->ndim; + int vdim = pdim - cdim; + assert(vdim == 2); int num_basis_conf = up->num_basis_conf; - struct gkyl_range vel_rng; - struct gkyl_range_iter conf_iter, vel_iter; - - int pidx[GKYL_MAX_DIM], rem_dir[GKYL_MAX_DIM] = { 0 }; - for (int d=0; dndim; ++d) rem_dir[d] = 1; - - double xc[GKYL_MAX_DIM], xmu[GKYL_MAX_DIM] = {0.0}; - double phi_quad[tot_quad_conf]; - double qDphiDbmag_quad[tot_quad_conf]; // charge*(phi-phi_m)/(bmag_max-bmag[0]). - - // Outer loop over configuration space cells; for each - // config-space cell inner loop walks over velocity space. - gkyl_range_iter_init(&conf_iter, conf_range); - while (gkyl_range_iter_next(&conf_iter)) { - long linidx_conf = gkyl_range_idx(conf_range, conf_iter.idx); - - const double *phi_d = gkyl_array_cfetch(phi, linidx_conf); - const double *Dbmag_quad = gkyl_array_cfetch(up->Dbmag_quad, linidx_conf); - - // Sum over basis for given potential phi. - for (int n=0; nbasis_at_ords_conf, n); - - // Compute the configuration-space quadrature - phi_quad[n] = 0.0; - for (int k=0; k 0.0) - qDphiDbmag_quad[n] = up->charge*(phi_quad[n]-phi_m[0])/Dbmag_quad[n]; - else - qDphiDbmag_quad[n] = 0.0; + // Tensor range over the phase-space P1 cell corners. The first cdim + // indices identify the configuration corner and the remaining indices the + // velocity corner, without assuming a flattened corner ordering. + int corner_shape[GKYL_MAX_DIM]; + for (int d = 0; d < pdim; ++d) { + corner_shape[d] = 2; + } + struct gkyl_range phase_corner_range; + gkyl_range_init_from_shape(&phase_corner_range, pdim, corner_shape); + + // Outer loop over all phase-space cells. + struct gkyl_range_iter phase_iter; + gkyl_range_iter_init(&phase_iter, phase_range); + while (gkyl_range_iter_next(&phase_iter)) { + int conf_idx[GKYL_MAX_CDIM] = { 0 }; + int vel_idx[GKYL_MAX_VDIM] = { 0 }; + for (int d = 0; d < cdim; ++d) { + conf_idx[d] = phase_iter.idx[d]; + } + for (int d = 0; d < vdim; ++d) { + vel_idx[d] = phase_iter.idx[cdim + d]; } - // Inner loop over velocity space. - gkyl_range_deflate(&vel_rng, phase_range, rem_dir, conf_iter.idx); - gkyl_range_iter_no_split_init(&vel_iter, &vel_rng); - while (gkyl_range_iter_next(&vel_iter)) { - - copy_idx_arrays(conf_range->ndim, phase_range->ndim, conf_iter.idx, vel_iter.idx, pidx); - long linidx_phase = gkyl_range_idx(&vel_rng, vel_iter.idx); - - // Compute the mask function at phase-space quadrature nodes. - struct gkyl_range_iter qiter; - gkyl_range_iter_init(&qiter, &up->phase_qrange); - while (gkyl_range_iter_next(&qiter)) { - - int cqidx = gkyl_range_idx(&up->conf_qrange, qiter.idx); - int pqidx = gkyl_range_idx(&up->phase_qrange, qiter.idx); - - const double *xcomp_d = gkyl_array_cfetch(up->ordinates_phase, pqidx); - - // Convert comp position coordinate to phys pos coord. - gkyl_rect_grid_cell_center(up->grid_phase, pidx, xc); - log_to_comp(up->cdim, xcomp_d, up->grid_phase->dx, xc, xmu); - - // Convert comp velocity coordinate to phys velocity coord. - const struct gkyl_velocity_map *gvm = up->vel_map; - long linidx_vel = gkyl_range_idx(&gvm->local_ext_vel, vel_iter.idx); - const double *vmap_d = gkyl_array_cfetch(gvm->vmap, linidx_vel); - double xcomp[1]; - for (int vd = 0; vd < vdim; vd++) { - xcomp[0] = xcomp_d[cdim+vd]; - xmu[cdim+vd] = gvm->vmap_basis->eval_expand(xcomp, vmap_d+vd*gvm->vmap_basis->num_basis); - } - - // KEparDbmag = 0.5*mass*pow(vpar,2)/(bmag_max-bmag[0]). - double KEparDbmag = 0.0; - if (Dbmag_quad[cqidx] > 0.0) - KEparDbmag = 0.5*up->mass*pow(xmu[cdim], 2.0)/Dbmag_quad[cqidx]; - else - KEparDbmag = 0.0; - - double mu_bound = GKYL_MAX2(0.0, KEparDbmag+qDphiDbmag_quad[cqidx]); - - double *fq = gkyl_array_fetch(up->fun_at_ords, pqidx); - if (mu_bound < xmu[cdim+1] && fabs(xmu[cdim-1]) < fabs(up->bmag_max_loc[cdim-1])) - fq[0] = 1.0 * up->norm_fac; - else - fq[0] = 0.0; - } - // Compute DG expansion coefficients of the mask. - if (up->cellwise_trap_loss) - nod_to_mod_reduce(up, up->fun_at_ords, gkyl_array_fetch(mask_out, linidx_phase)); - else - proj_on_basis(up, up->fun_at_ords, gkyl_array_fetch(mask_out, linidx_phase)); + const struct gkyl_velocity_map *gvm = up->vel_map; + long linidx_vel = gkyl_range_idx(&gvm->local_ext_vel, vel_idx); + const double *vmap_d = gkyl_array_cfetch(gvm->vmap, linidx_vel); + + // A cell is trapped only if all of its P1 corners are trapped. + bool cell_trapped = true; + struct gkyl_range_iter corner_iter; + gkyl_range_iter_init(&corner_iter, &phase_corner_range); + while (cell_trapped && gkyl_range_iter_next(&corner_iter)) { + int conf_corner = gkyl_range_idx(&up->conf_corner_range, corner_iter.idx); + + double vpar_eta[1] = { corner_iter.idx[cdim] ? 1.0 : -1.0 }; + double mu_eta[1] = { corner_iter.idx[cdim + 1] ? 1.0 : -1.0 }; + double vpar = gvm->vmap_basis->eval_expand(vpar_eta, vmap_d); + double mu = gvm->vmap_basis->eval_expand(mu_eta, + vmap_d + gvm->vmap_basis->num_basis); + + // Evaluate the Hamiltonian at this phase-space corner. + long linidx_conf = gkyl_range_idx(conf_range, conf_idx); + double bmag_curr = gk_lcm_field_corner_value(bmag, up->basis_at_corners_conf, + num_basis_conf, linidx_conf, conf_corner); + double phi_curr = gk_lcm_field_corner_value(phi, up->basis_at_corners_conf, + num_basis_conf, linidx_conf, conf_corner); + double kinetic_energy = 0.5 * up->mass * vpar * vpar; + double magnetic_energy = mu * bmag_curr; + double electric_energy = up->charge * phi_curr; + + // Determine both directional escape barriers at this corner. + int zdim = cdim - 1; + double barrier_left, barrier_right; + gk_lcm_escape_barriers(cdim, num_basis_conf, conf_range, + &up->conf_corner_range, up->basis_at_corners_conf, phi, bmag, + phi_wall_lo, phi_wall_up, conf_idx, conf_idx[zdim], conf_corner, + mu, up->charge, up->lower_orbit, up->upper_orbit, + &barrier_left, &barrier_right); + + // Equality means that the particle reaches the loss endpoint with zero + // parallel kinetic energy, so it is not trapped. + cell_trapped = gk_lcm_hamiltonian_below_barrier(kinetic_energy, + magnetic_energy, electric_energy, barrier_left) + && gk_lcm_hamiltonian_below_barrier(kinetic_energy, magnetic_energy, + electric_energy, barrier_right); } + + long linidx_phase = gkyl_range_idx(phase_range, phase_iter.idx); + double *mask_d = gkyl_array_fetch(mask_out, linidx_phase); + mask_d[0] = cell_trapped ? 1.0 : 0.0; } } void -gkyl_loss_cone_mask_gyrokinetic_release(gkyl_loss_cone_mask_gyrokinetic* up) +gkyl_loss_cone_mask_gyrokinetic_release(gkyl_loss_cone_mask_gyrokinetic *up) { gkyl_velocity_map_release(up->vel_map); - - gkyl_array_release(up->ordinates_phase); - gkyl_array_release(up->weights_phase); - gkyl_array_release(up->basis_at_ords_phase); - - gkyl_array_release(up->ordinates_conf); - gkyl_array_release(up->weights_conf); - gkyl_array_release(up->basis_at_ords_conf); - - gkyl_array_release(up->fun_at_ords); - gkyl_array_release(up->Dbmag_quad); - - if (up->use_gpu) { - gkyl_cu_free(up->p2c_qidx); - gkyl_array_release(up->mask_out_quad); - gkyl_array_release(up->qDphiDbmag_quad); - gkyl_mat_mm_array_mem_release(up->phase_nodal_to_modal_mem); - gkyl_cu_free(up->bmag_max_loc); - } - else { - gkyl_free(up->bmag_max_loc); - } - + gkyl_array_release(up->basis_at_corners_conf); gkyl_free(up); } diff --git a/gyrokinetic/zero/loss_cone_mask_gyrokinetic_cu.cu b/gyrokinetic/zero/loss_cone_mask_gyrokinetic_cu.cu index e38c8780c5..ed345761c2 100644 --- a/gyrokinetic/zero/loss_cone_mask_gyrokinetic_cu.cu +++ b/gyrokinetic/zero/loss_cone_mask_gyrokinetic_cu.cu @@ -1,277 +1,119 @@ /* -*- c++ -*- */ -#include -#include +#include extern "C" { -#include #include -#include -#include -#include #include #include #include - -#include -#include -} - -__global__ static void -gkyl_loss_cone_mask_gyrokinetic_Dbmag_quad_cu_ker(struct gkyl_range conf_range, - const struct gkyl_array* basis_at_ords_conf, const struct gkyl_array* bmag, const double *bmag_max, - struct gkyl_array* Dbmag_quad_d) -{ - int num_basis_conf = basis_at_ords_conf->ncomp; - int tot_quad_conf = basis_at_ords_conf->size; - - int cidx[GKYL_MAX_CDIM]; - - for(unsigned long tid = threadIdx.x + blockIdx.x*blockDim.x; - tid < conf_range.volume; tid += blockDim.x*gridDim.x) { - - gkyl_sub_range_inv_idx(&conf_range, tid, cidx); - long linidx = gkyl_range_idx(&conf_range, cidx); - - const double *bmag_d = (const double*) gkyl_array_cfetch(bmag, linidx); - - double *bmag_quad = (double*) gkyl_array_fetch(Dbmag_quad_d, linidx); - - for (int n=0; nnblocks, nthreads = conf_range->nthreads; - gkyl_loss_cone_mask_gyrokinetic_Dbmag_quad_cu_ker<<>>(*conf_range, - up->basis_at_ords_conf->on_dev, bmag->on_dev, bmag_max, up->Dbmag_quad->on_dev); -} - -static void -gkyl_parallelize_components_kernel_launch_dims(dim3* dimGrid, dim3* dimBlock, gkyl_range range, int ncomp) -{ - // Create a 2D thread grid so we launch ncomp*range.volume number of threads - // so we can parallelize over components too - dimBlock->y = ncomp; // ncomp *must* be less than 256 - dimGrid->y = 1; - dimBlock->x = GKYL_DEFAULT_NUM_THREADS/ncomp; - dimGrid->x = gkyl_int_div_up(range.volume, dimBlock->x); -} - -__global__ static void -gkyl_loss_cone_mask_gyrokinetic_qDphiDbmag_quad_ker(struct gkyl_range conf_range, - const struct gkyl_array* basis_at_ords_conf, double charge, const struct gkyl_array* phi, - const double *phi_m, const struct gkyl_array* Dbmag_quad, struct gkyl_array* qDphiDbmag_quad) -{ - int num_basis_conf = basis_at_ords_conf->ncomp; - - int cidx[GKYL_MAX_CDIM]; - - // 2D thread grid - // linc2 goes from 0 to tot_quad_conf= basis_at_ords_conf->size. - long linc2 = threadIdx.y + blockIdx.y*blockDim.y; - for(unsigned long tid = threadIdx.x + blockIdx.x*blockDim.x; - tid < conf_range.volume; tid += blockDim.x*gridDim.x) { - gkyl_sub_range_inv_idx(&conf_range, tid, cidx); - - long linidx = gkyl_range_idx(&conf_range, cidx); - - const double *phi_d = (const double*) gkyl_array_cfetch(phi, linidx); - const double *Dbmag_quad_d = (const double*) gkyl_array_cfetch(Dbmag_quad, linidx); - - // Sum over basis at configuration-space quadrature points. - const double *b_ord = (const double*) gkyl_array_cfetch(basis_at_ords_conf, linc2); - double phi_quad = 0; - for (int k=0; k 0.0) - qDphiDbmag_quad_d[linc2] = charge*(phi_quad-phi_m[0])/Dbmag_quad_d[linc2]; - else - qDphiDbmag_quad_d[linc2] = 0.0; - } +#include } __global__ static void -gkyl_loss_cone_mask_gyrokinetic_ker(struct gkyl_rect_grid grid_phase, - struct gkyl_range phase_range, struct gkyl_range conf_range, struct gkyl_range vel_range, - double mass, const struct gkyl_array* phase_ordinates, - const double *bmag_max_loc, const struct gkyl_array* qDphiDbmag_quad, const struct gkyl_array* Dbmag_quad, - const int *p2c_qidx, struct gkyl_array* vmap, struct gkyl_basis* vmap_basis, struct gkyl_array* mask_out) +gkyl_loss_cone_mask_gyrokinetic_advance_cu_ker(int cdim, int num_basis_conf, + struct gkyl_range conf_corner_range, struct gkyl_range phase_corner_range, + double mass, double charge, + struct gkyl_range phase_range, struct gkyl_range conf_range, + const struct gkyl_array *basis_at_corners_conf, + const struct gkyl_array *bmag, const struct gkyl_array *phi, + const struct gkyl_array *phi_wall_lo, const struct gkyl_array *phi_wall_up, + enum gkyl_gk_trapped_passing_orbit_type lower_orbit, + enum gkyl_gk_trapped_passing_orbit_type upper_orbit, + const struct gkyl_velocity_map *gvm, + struct gkyl_array *mask_out) { - int pdim = phase_range.ndim, cdim = conf_range.ndim; - int vdim = pdim-cdim; - - double xc[GKYL_MAX_DIM], xmu[GKYL_MAX_DIM] = {0.0}; - int pidx[GKYL_MAX_DIM], cidx[GKYL_MAX_CDIM], vidx[2]; - - int tot_phase_quad = phase_ordinates->size; - - for(unsigned long tid = threadIdx.x + blockIdx.x*blockDim.x; - tid < phase_range.volume; tid += blockDim.x*gridDim.x) { - gkyl_sub_range_inv_idx(&phase_range, tid, pidx); - - // Get configuration-space linear index. - for (unsigned int k = 0; k < cdim; k++) cidx[k] = pidx[k]; - long linidx_conf = gkyl_range_idx(&conf_range, cidx); - - const double *Dbmag_quad_d = (const double*) gkyl_array_cfetch(Dbmag_quad, linidx_conf); - const double *qDphiDbmag_quad_d = (const double*) gkyl_array_cfetch(qDphiDbmag_quad, linidx_conf); - - gkyl_rect_grid_cell_center(&grid_phase, pidx, xc); - long linidx_phase = gkyl_range_idx(&phase_range, pidx); - double *mask_d = (double*) gkyl_array_fetch(mask_out, linidx_phase); + int pdim = phase_range.ndim; + int vdim = pdim - cdim; - for (int d = cdim; d < pdim; d++) vidx[d-cdim] = pidx[d]; - long linidx_vel = gkyl_range_idx(&vel_range, vidx); - const double *vmap_d = (const double*) gkyl_array_cfetch(vmap, linidx_vel); + int phase_idx[GKYL_MAX_DIM]; + int conf_idx[GKYL_MAX_CDIM] = { 0 }; + int vel_idx[GKYL_MAX_VDIM] = { 0 }; - mask_d[0] = 1.0; // In this case the mask has ncomp=1. + for (unsigned long tid = threadIdx.x + blockIdx.x * blockDim.x; + tid < phase_range.volume; tid += blockDim.x * gridDim.x) { - for (int n=0; neval_expand(xcomp, vmap_d+vd*vmap_basis->num_basis); - } - - // KEparDbmag = 0.5*mass*pow(vpar,2)/(bmag_max-bmag[0]). - double KEparDbmag = 0.0; - if (Dbmag_quad_d[cqidx] > 0.0) - KEparDbmag = 0.5*mass*pow(xmu[cdim], 2.0)/Dbmag_quad_d[cqidx]; - else - KEparDbmag = 0.0; - - double mu_bound = GKYL_MAX2(0.0, KEparDbmag+qDphiDbmag_quad_d[cqidx]); - - if ( !(mu_bound < xmu[cdim+1] && fabs(xmu[cdim-1]) < fabs(bmag_max_loc[cdim-1])) ) { - mask_d[0] = 0.0; - break; - } + for (int d = 0; d < cdim; ++d) { + conf_idx[d] = phase_idx[d]; } - } -} - -__global__ static void -gkyl_loss_cone_mask_gyrokinetic_quad_ker(struct gkyl_rect_grid grid_phase, - struct gkyl_range phase_range, struct gkyl_range conf_range, struct gkyl_range vel_range, - double mass, double norm_fac, const struct gkyl_array* phase_ordinates, - const double *bmag_max_loc, const struct gkyl_array* qDphiDbmag_quad, const struct gkyl_array* Dbmag_quad, - const int *p2c_qidx, struct gkyl_array* vmap, struct gkyl_basis* vmap_basis, struct gkyl_array* mask_out_quad) -{ - int pdim = phase_range.ndim, cdim = conf_range.ndim; - int vdim = pdim-cdim; - - double xc[GKYL_MAX_DIM], xmu[GKYL_MAX_DIM] = {0.0}; - int pidx[GKYL_MAX_DIM], cidx[GKYL_MAX_CDIM], vidx[2]; - - // 2D thread grid - // linc2 goes from 0 to tot_quad_phase - long linc2 = threadIdx.y + blockIdx.y*blockDim.y; - for(unsigned long tid = threadIdx.x + blockIdx.x*blockDim.x; - tid < phase_range.volume; tid += blockDim.x*gridDim.x) { - gkyl_sub_range_inv_idx(&phase_range, tid, pidx); - - // Get configuration-space linear index. - for (unsigned int k = 0; k < cdim; k++) cidx[k] = pidx[k]; - - long linidx_conf = gkyl_range_idx(&conf_range, cidx); - - const double *Dbmag_quad_d = (const double*) gkyl_array_cfetch(Dbmag_quad, linidx_conf); - const double *qDphiDbmag_quad_d = (const double*) gkyl_array_cfetch(qDphiDbmag_quad, linidx_conf); - - gkyl_rect_grid_cell_center(&grid_phase, pidx, xc); - long linidx_phase = gkyl_range_idx(&phase_range, pidx); - - int cqidx = p2c_qidx[linc2]; - for (int d = cdim; d < pdim; d++) vidx[d-cdim] = pidx[d]; - - long linidx_vel = gkyl_range_idx(&vel_range, vidx); - const double *vmap_d = (const double*) gkyl_array_cfetch(vmap, linidx_vel); - const double *xcomp_d = (const double*) gkyl_array_cfetch(phase_ordinates, linc2); - - // Convert comp position coordinate to phys pos coord. - gkyl_rect_grid_cell_center(&grid_phase, pidx, xc); - log_to_comp(cdim, xcomp_d, grid_phase.dx, xc, xmu); - - // Convert comp velocity coordinate to phys velocity coord. - double xcomp[1]; - for (int vd = 0; vd < vdim; vd++) { - xcomp[0] = xcomp_d[cdim+vd]; - xmu[cdim+vd] = vmap_basis->eval_expand(xcomp, vmap_d+vd*vmap_basis->num_basis); + for (int d = 0; d < vdim; ++d) { + vel_idx[d] = phase_idx[cdim + d]; } - // KEparDbmag = 0.5*mass*pow(vpar,2)/(bmag_max-bmag[0]). - double KEparDbmag = 0.0; - if (Dbmag_quad_d[cqidx] > 0.0) - KEparDbmag = 0.5*mass*pow(xmu[cdim], 2.0)/Dbmag_quad_d[cqidx]; - else - KEparDbmag = 0.0; - - double mu_bound = GKYL_MAX2(0.0, KEparDbmag+qDphiDbmag_quad_d[cqidx]); + long linidx_vel = gkyl_range_idx(&gvm->local_ext_vel, vel_idx); + const double *vmap_d = (const double *)gkyl_array_cfetch(gvm->vmap, linidx_vel); + + bool cell_trapped = true; + for (long corner = 0; corner < phase_corner_range.volume && cell_trapped; ++corner) { + int corner_idx[GKYL_MAX_DIM] = { 0 }; + gkyl_range_inv_idx(&phase_corner_range, corner, corner_idx); + int conf_corner = gkyl_range_idx(&conf_corner_range, corner_idx); + + double vpar_eta[1] = { corner_idx[cdim] ? 1.0 : -1.0 }; + double mu_eta[1] = { corner_idx[cdim + 1] ? 1.0 : -1.0 }; + double vpar = gvm->vmap_basis->eval_expand(vpar_eta, vmap_d); + double mu = gvm->vmap_basis->eval_expand(mu_eta, + vmap_d + gvm->vmap_basis->num_basis); + + long linidx_conf = gkyl_range_idx(&conf_range, conf_idx); + double bmag_curr = gk_lcm_field_corner_value(bmag, basis_at_corners_conf, + num_basis_conf, linidx_conf, conf_corner); + double phi_curr = gk_lcm_field_corner_value(phi, basis_at_corners_conf, + num_basis_conf, linidx_conf, conf_corner); + double kinetic_energy = 0.5 * mass * vpar * vpar; + double magnetic_energy = mu * bmag_curr; + double electric_energy = charge * phi_curr; + + int zdim = cdim - 1; + double barrier_left, barrier_right; + gk_lcm_escape_barriers(cdim, num_basis_conf, &conf_range, &conf_corner_range, + basis_at_corners_conf, phi, bmag, phi_wall_lo, phi_wall_up, conf_idx, + conf_idx[zdim], conf_corner, mu, charge, lower_orbit, upper_orbit, + &barrier_left, &barrier_right); + + cell_trapped = gk_lcm_hamiltonian_below_barrier(kinetic_energy, + magnetic_energy, electric_energy, barrier_left) + && gk_lcm_hamiltonian_below_barrier(kinetic_energy, magnetic_energy, + electric_energy, barrier_right); + } - double *fq = (double*) gkyl_array_fetch(mask_out_quad, linidx_phase); - if (mu_bound < xmu[cdim+1] && fabs(xmu[cdim-1]) < fabs(bmag_max_loc[cdim-1])) - fq[linc2] = norm_fac; - else - fq[linc2] = 0.0; + long linidx_phase = gkyl_range_idx(&phase_range, phase_idx); + double *mask_d = (double *)gkyl_array_fetch(mask_out, linidx_phase); + mask_d[0] = cell_trapped ? 1.0 : 0.0; } } void gkyl_loss_cone_mask_gyrokinetic_advance_cu(gkyl_loss_cone_mask_gyrokinetic *up, const struct gkyl_range *phase_range, const struct gkyl_range *conf_range, - const struct gkyl_array *phi, const double *phi_m, struct gkyl_array *mask_out) + const struct gkyl_array *bmag, const struct gkyl_array *phi, + const struct gkyl_array *phi_wall_lo, const struct gkyl_array *phi_wall_up, + struct gkyl_array *mask_out) { - dim3 dimGrid_conf, dimBlock_conf; - int tot_quad_conf = up->basis_at_ords_conf->size; - gkyl_parallelize_components_kernel_launch_dims(&dimGrid_conf, &dimBlock_conf, *conf_range, tot_quad_conf); + int pdim = phase_range->ndim; + int vdim = pdim - up->cdim; + assert(vdim == 2); - gkyl_loss_cone_mask_gyrokinetic_qDphiDbmag_quad_ker<<>>(*conf_range, - up->basis_at_ords_conf->on_dev, up->charge, phi->on_dev, phi_m, up->Dbmag_quad->on_dev, - up->qDphiDbmag_quad->on_dev); + int corner_shape[GKYL_MAX_DIM]; + for (int d = 0; d < pdim; ++d) { + corner_shape[d] = 2; + } + struct gkyl_range phase_corner_range; + gkyl_range_init_from_shape(&phase_corner_range, pdim, corner_shape); const struct gkyl_velocity_map *gvm = up->vel_map; - if (up->cellwise_trap_loss) { - // Don't do quadrature. - int nblocks = phase_range->nblocks, nthreads = phase_range->nthreads; - gkyl_loss_cone_mask_gyrokinetic_ker<<>>(*up->grid_phase, *phase_range, *conf_range, - gvm->local_ext_vel, up->mass, up->ordinates_phase->on_dev, - up->bmag_max_loc, up->qDphiDbmag_quad->on_dev, up->Dbmag_quad->on_dev, up->p2c_qidx, gvm->vmap->on_dev, - gvm->vmap_basis, mask_out->on_dev); - } - else { - // Use quadrature. - dim3 dimGrid, dimBlock; - int tot_quad_phase = up->basis_at_ords_phase->size; - gkyl_parallelize_components_kernel_launch_dims(&dimGrid, &dimBlock, *phase_range, tot_quad_phase); + int nblocks = phase_range->nblocks; + int nthreads = phase_range->nthreads; - gkyl_loss_cone_mask_gyrokinetic_quad_ker<<>>(*up->grid_phase, *phase_range, *conf_range, - gvm->local_ext_vel, up->mass, up->norm_fac, up->ordinates_phase->on_dev, - up->bmag_max_loc, up->qDphiDbmag_quad->on_dev, up->Dbmag_quad->on_dev, up->p2c_qidx, gvm->vmap->on_dev, - gvm->vmap_basis, up->mask_out_quad->on_dev); + const struct gkyl_array *phi_wall_lo_dev = phi_wall_lo ? phi_wall_lo->on_dev : 0; + const struct gkyl_array *phi_wall_up_dev = phi_wall_up ? phi_wall_up->on_dev : 0; - // Call cublas to do the matrix multiplication nodal to modal conversion - gkyl_mat_mm_array(up->phase_nodal_to_modal_mem, up->mask_out_quad, mask_out); - } + gkyl_loss_cone_mask_gyrokinetic_advance_cu_ker<<>>( + up->cdim, up->num_basis_conf, up->conf_corner_range, phase_corner_range, + up->mass, up->charge, *phase_range, *conf_range, + up->basis_at_corners_conf->on_dev, bmag->on_dev, phi->on_dev, + phi_wall_lo_dev, phi_wall_up_dev, up->lower_orbit, up->upper_orbit, + gvm->on_dev, mask_out->on_dev); }