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..67c505057e 100644 --- a/gyrokinetic/apps/gk_species.c +++ b/gyrokinetic/apps/gk_species.c @@ -211,7 +211,7 @@ 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: 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); @@ -233,7 +233,7 @@ 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: 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); @@ -1497,6 +1497,8 @@ 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; } } + + gk_species_wall_potential_init(app, gks); // Species properties metadata. struct gkyl_msgpack_map_elem io_meta_sprop[] = { @@ -2036,6 +2038,8 @@ gk_species_release(const gkyl_gyrokinetic_app* app, const struct gk_species *gks gk_species_fdot_multiplier_release(app, &gks->fdot_mult); + gk_species_wall_potential_release(app, gks); + gk_species_lbo_release(app, &gks->lbo); gk_species_bgk_release(app, &gks->bgk); diff --git a/gyrokinetic/apps/gk_species_damping.c b/gyrokinetic/apps/gk_species_damping.c index 16669672d2..ce9e0558e5 100644 --- a/gyrokinetic/apps/gk_species_damping.c +++ b/gyrokinetic/apps/gk_species_damping.c @@ -1,16 +1,24 @@ #include -#include -#include #include #include +#include + +static void +proj_on_basis_c2p_position_func(const double *xcomp, double *xphys, void *ctx) +{ + struct gk_proj_on_basis_c2p_func_ctx *c2p_ctx = ctx; + gkyl_position_map_eval_mc2nu(c2p_ctx->pos_map, xcomp, xphys); +} 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 +29,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 +39,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 +49,13 @@ 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; @@ -72,127 +82,40 @@ gk_species_damping_init(struct gkyl_gyrokinetic_app *app, struct gk_species *gks // Default function pointers. damp->write_func = gk_species_damping_write_disabled; + damp->proj_on_basis_c2p_ctx.cdim = app->cdim; + damp->proj_on_basis_c2p_ctx.vdim = gks->local_vel.ndim; + damp->proj_on_basis_c2p_ctx.vel_map = gks->vel_map; + damp->proj_on_basis_c2p_ctx.pos_map = app->position_map; + 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. proj_on_basis_c2p_ctx.cdim = app->cdim; proj_on_basis_c2p_ctx.vdim = gks->local_vel.ndim; proj_on_basis_c2p_ctx.vel_map = gks->vel_map; - gkyl_proj_on_basis *projup = gkyl_proj_on_basis_inew( &(struct gkyl_proj_on_basis_inp) { - .grid = &gks->grid, - .basis = &gks->basis, - .num_quad = num_quad, - .num_ret_vals = 1, - .eval = gks->info.damping.rate_profile, - .ctx = gks->info.damping.rate_profile_ctx, - .c2p_func = proj_on_basis_c2p_phase_func, - .c2p_func_ctx = &proj_on_basis_c2p_ctx, - } - ); + gkyl_proj_on_basis *projup = gkyl_proj_on_basis_inew(&(struct gkyl_proj_on_basis_inp) { + .grid = &gks->grid, + .basis = &gks->basis, + .num_quad = num_quad, + .num_ret_vals = 1, + .eval = gks->info.damping.rate_profile, + .ctx = gks->info.damping.rate_profile_ctx, + .c2p_func = proj_on_basis_c2p_phase_func, + .c2p_func_ctx = &proj_on_basis_c2p_ctx, + }); gkyl_proj_on_basis_advance(projup, 0.0, &gks->local, damp->rate_host); gkyl_proj_on_basis_release(projup); gkyl_array_copy(damp->rate, damp->rate_host); 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); + gkyl_array_scale_range(damp->rate, 1.0 / pow(sqrt(2.0), gks->grid.ndim), &gks->local); } // Set function pointers chosen at runtime. @@ -206,7 +129,8 @@ 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 +141,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 +150,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 +160,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..cc9d74a13d 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_loss_cone_boundary_type +loss_cone_boundary_from_species_bc(enum gkyl_gyrokinetic_bc_type bc) +{ + switch (bc) { + case GKYL_BC_GK_SPECIES_SHEATH: + return GKYL_LOSS_CONE_BC_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_LOSS_CONE_BC_CLOSED; + default: + return GKYL_LOSS_CONE_BC_OPEN; + } +} 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_boundary = loss_cone_boundary_from_species_bc(gks->lower_bc[zdim].type), + .upper_boundary = loss_cone_boundary_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_boundary == GKYL_LOSS_CONE_BC_SHEATH) + fdmul->phi_wall_lo_global = mkarr(app->use_gpu, app->basis.num_basis, + app->global_ext.volume); + if (inp_proj.upper_boundary == GKYL_LOSS_CONE_BC_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_wall_potential.c b/gyrokinetic/apps/gk_species_wall_potential.c new file mode 100644 index 0000000000..befc2c082e --- /dev/null +++ b/gyrokinetic/apps/gk_species_wall_potential.c @@ -0,0 +1,74 @@ +#include +#include + +static void +wall_potential_advance(gkyl_gyrokinetic_app *app, + struct gk_species_wall_potential *wall, double tm) +{ + if (wall->projector) { + 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); + } +} + +static void +wall_potential_init(gkyl_gyrokinetic_app *app, + const struct gkyl_gyrokinetic_bc *bc, struct gk_species_wall_potential *wall) +{ + *wall = (struct gk_species_wall_potential) { }; + 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_potential_advance(app, wall, 0.0); + } +} + +static void +wall_potential_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); + } +} + +void +gk_species_wall_potential_init(gkyl_gyrokinetic_app *app, struct gk_species *species) +{ + int par_dir = app->cdim-1; + wall_potential_init(app, &species->lower_bc[par_dir], &species->phi_wall_lo); + wall_potential_init(app, &species->upper_bc[par_dir], &species->phi_wall_up); +} + +void +gk_species_wall_potential_advance(gkyl_gyrokinetic_app *app, + struct gk_species *species, double tm) +{ + wall_potential_advance(app, &species->phi_wall_lo, tm); + wall_potential_advance(app, &species->phi_wall_up, tm); +} + +void +gk_species_wall_potential_release(const gkyl_gyrokinetic_app *app, + const struct gk_species *species) +{ + wall_potential_release(app, &species->phi_wall_lo); + wall_potential_release(app, &species->phi_wall_up); +} 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..31938b0e63 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. @@ -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..7d557e423d 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h @@ -843,11 +843,7 @@ 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. + struct gk_proj_on_basis_c2p_func_ctx proj_on_basis_c2p_ctx; // c2p function context. // Functions chosen at runtime. void (*write_func)(gkyl_gyrokinetic_app* app, struct gk_species *gks, double tm, int frame); }; @@ -858,10 +854,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 +999,13 @@ 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; +}; + // Species data. struct gk_species { struct gkyl_gyrokinetic_species info; // Input data. @@ -1062,6 +1068,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; @@ -1368,18 +1376,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); @@ -3340,6 +3336,33 @@ 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 the wall potentials owned by a species' parallel sheath BCs. + * + * @param app Gyrokinetic app object. + * @param species Species object. + */ +void gk_species_wall_potential_init(gkyl_gyrokinetic_app *app, struct gk_species *species); + +/** + * Evaluate a species' sheath wall-potential profiles at the requested time. + * + * @param app Gyrokinetic app object. + * @param species Species object. + * @param tm Time at which to evaluate the profiles. + */ +void gk_species_wall_potential_advance(gkyl_gyrokinetic_app *app, + struct gk_species *species, double tm); + +/** + * Release a species' sheath wall-potential resources. + * + * @param app Gyrokinetic app object. + * @param species Species object. + */ +void gk_species_wall_potential_release(const gkyl_gyrokinetic_app *app, + const struct gk_species *species); + /** gk_neut_species_moment API */ /** @@ -4135,15 +4158,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..c18c9bf6f2 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); @@ -982,6 +976,9 @@ void gyrokinetic_calc_field(gkyl_gyrokinetic_app* app, double tcurr, const struct gkyl_array *fin[], struct gkyl_array **bflux[]) { + for (int i=0; inum_species; ++i) + gk_species_wall_potential_advance(app, &app->species[i], tcurr); + app->calc_field_func(app, tcurr, fin, bflux); } @@ -1061,6 +1058,9 @@ gkyl_gyrokinetic_app_apply_ic(gkyl_gyrokinetic_app* app, double t0) for (int i=0; inum_neut_species; ++i) gkyl_gyrokinetic_app_apply_ic_cross_neut_species(app, i, t0); + for (int i=0; inum_species; ++i) + gk_species_wall_potential_advance(app, &app->species[i], t0); + // Compute the fields and apply BCs. struct gkyl_array *distf[app->num_species]; struct gkyl_array **bflux[app->num_species]; diff --git a/gyrokinetic/apps/gyrokinetic_multib.c b/gyrokinetic/apps/gyrokinetic_multib.c index de0352dee2..cfccffc254 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++) { + for (int i=0; i<2*GKYL_MAX_CDIM; i++) neut_species_inp.bcs[i].type = GKYL_BC_GK_SKIP; - neut_species_inp.bcs[i].type = GKYL_BC_GK_SKIP; - } // Set species physical BCs. int bc_count_nsp[num_blocks]; @@ -353,15 +349,6 @@ singleb_app_new_solver(const struct gkyl_gyrokinetic_multib *mbinp, int bid, const struct gkyl_gyrokinetic_multib_field_pb *fld_pb = &fld->blocks[0]; // Choose proper block-specific field input. - if (!fld->duplicate_across_blocks) { - for (int i=0; iblocks[i].block_id) { - const struct gkyl_gyrokinetic_multib_field_pb *fld_pb = &fld->blocks[i]; - break; - } - } - } - if (!fld->duplicate_across_blocks) { for (int i=0; iblocks[i].block_id) { @@ -374,15 +361,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,6 +910,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_wall_potential_advance(sbapp, &sbapp->species[i], tcurr); gk_species_apply_bc(sbapp, &sbapp->species[i], distf[li_charged+i]); } for (int i=0; inum_neut_species; ++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_1x2v_p1_2species.c b/gyrokinetic/creg/rt_gk_mirror_boltz_elc_poa_1x2v_p1_2species.c new file mode 100644 index 0000000000..d261b047b4 --- /dev/null +++ b/gyrokinetic/creg/rt_gk_mirror_boltz_elc_poa_1x2v_p1_2species.c @@ -0,0 +1,1030 @@ +#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 vte; // Electron thermal speed. + 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_eval; + // Physics parameters at mirror throat + double vpar_max_ion; + double mu_max_ion; + double vpar_max_elc; // Electron velocity-space extents. + double mu_max_elc; + int Npsi; + int Nz; + int Nvpar; + int Nmu; + int Nvpar_elc; // Electron velocity-space cells. + int Nmu_elc; + 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[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) +{ + 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[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] = 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[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) +{ + 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); +} + +// Electron species support (added to deform toward rt_gk_failure). +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_zero(double t, const double *GKYL_RESTRICT xn, double *GKYL_RESTRICT fout, void *ctx) +{ + fout[0] = 0.0; +} + +void mapc2p_vel_elc(double t, const double *vc, double *GKYL_RESTRICT vp, void *ctx) +{ + struct gk_mirror_ctx *app = ctx; + double mu_max_elc = app->mu_max_elc; + double vpar_max_elc = app->vpar_max_elc; + double cvpar = vc[0], cmu = vc[1]; + vp[0] = vpar_max_elc * cvpar; + vp[1] = mu_max_elc * pow(cmu, 4); // Quartic map in mu. +} + +struct gk_mirror_ctx +create_ctx(void) +{ + int cdim = 1, 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); + double vte = sqrt(Te0 / me); + + // Grid parameters + double vpar_max_ion = 16 * vti; + double mu_max_ion = mi * pow(3. * vti, 2.) / (2. * B_p); + double vpar_max_elc = 4 * vte; + double mu_max_elc = me * pow(4. * vte, 2.) / (2. * B_p); + int Nz = 64; + int Nvpar = 32; // 96 uniform + int Nmu = 16; // 192 uniform + int Nvpar_elc = 16; + int Nmu_elc = 8; + + // 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; + + // Duration of each phase. + double tau_oap = 100e-9; + double tau_fdp = 1e-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 = 1; // 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 = 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; + } + // 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, + .vte = vte, + .RatZeq0 = RatZeq0, + .vpar_max_ion = vpar_max_ion, + .mu_max_ion = mu_max_ion, + .vpar_max_elc = vpar_max_elc, + .mu_max_elc = mu_max_elc, + .Nz = Nz, + .Nvpar = Nvpar, + .Nmu = Nmu, + .Nvpar_elc = Nvpar_elc, + .Nmu_elc = Nmu_elc, + .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, + + .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); + + 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); + 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); + + 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, // So solvers are allocated. + .cellwise_const = true, + .write_diagnostics = true, + }, + }, + + .collisions = { + .collision_id = GKYL_LBO_COLLISIONS, + .den_ref = ctx.n0, + .temp_ref = ctx.Ti0, + .write_diagnostics = true, + .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_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_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 }, + }, + }; + // Second species (Boltzmann-scaled kinetic electrons), added to deform toward + // rt_gk_failure. It has no fdot multiplier; only "ion" is reset each phase. + 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 = { ctx.Nvpar_elc, ctx.Nmu_elc }, + + .polarization_density = ctx.n0, + + .mapc2p = { + .mapping = mapc2p_vel_elc, + .ctx = &ctx, + }, + + .projection = { + .proj_id = GKYL_PROJ_MAXWELLIAN_PRIM, + .density = eval_density_ion, + .ctx_density = &ctx, + .upar = eval_zero, + .ctx_upar = &ctx, + .temp = eval_temp_elc, + .ctx_temp = &ctx, + .correct_all_moms = true, + }, + + .collisions = { + .collision_id = GKYL_LBO_COLLISIONS, + .num_cross_collisions = 1, + .collide_with = { "ion" }, + .den_ref = ctx.n0, + .temp_ref = ctx.Te0, + .not_in_dfdt = true, + .write_diagnostics = true, + }, + + .scaling = { + .type = GKYL_GK_SPECIES_SCALING_BOLTZMANN, + }, + + .num_diag_moments = 1, + .diag_moments = { GKYL_F_MOMENT_MAXWELLIAN }, + }; + + 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_mirror_geo_grid_inp grid_inp = { + .filename_psi = "/global/homes/m/mhrosen/scratch/gkylmax/generate_efit/lorentzian_R32.geqdsk_psi.gkyl", // psi file to use + .rclose = 0.2, // closest R to region of interest + .zmin = -2.5, // Z of lower boundary + .zmax = 2.5, // Z of upper boundary + .include_axis = false, // Include R=0 axis in grid + .fl_coord = GKYL_GEOMETRY_MIRROR_GRID_GEN_SQRT_PSI_CART_Z, // coordinate system for psi grid + }; + + struct gkyl_gk app_inp = { // GK app + .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, + .position_map_info = { + .id = GKYL_PMAP_CONSTANT_DB_NUMERIC, + .map_strength = 0.5, + .maximum_slope_at_min_B = 2, + .gaussian_std = 0.25, + .gaussian_max_integration_width = 0.5, + }, + }, + + + .num_periodic_dir = 0, + .periodic_dirs = {}, + + .num_species = 2, + .species = { ion, elc }, + + .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_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..8943364ad2 --- /dev/null +++ b/gyrokinetic/creg/rt_gk_mirror_kinetic_elc_poa_1x2v_p1.c @@ -0,0 +1,1274 @@ +#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); + 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 = 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..8944f5be72 --- /dev/null +++ b/gyrokinetic/unit/ctest_gk_species_wall_potential.c @@ -0,0 +1,138 @@ +#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) +{ + 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_wall_potential_init(&app, &species); + 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); + 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_wall_potential_advance(&app, &species, tm); + check_profile(&app, species.phi_wall_lo.phi, &lower_ctx, tm); + check_profile(&app, species.phi_wall_up.phi, &upper_ctx, tm); + + gk_species_wall_potential_release(&app, &species); +} + +static void +test_grounded_and_non_sheath_walls(void) +{ + 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_wall_potential_init(&app, &species); + 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_CHECK(species.phi_wall_up.phi == 0); + TEST_CHECK(species.phi_wall_up.phi_host == 0); + TEST_CHECK(species.phi_wall_up.projector == 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 +#define _USE_MATH_DEFINES +#include +#include +#include +#include + #include #include #include -#include -#include -#include +#include +#include #include #include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif 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) +mapc2p_3x(double t, const double *xc, double *GKYL_RESTRICT xp, void *ctx) { - xp[0] = xc[0]; xp[1] = xc[1]; xp[2] = xc[2]; + xp[0] = xc[0]; + xp[1] = xc[1]; + xp[2] = xc[2]; } void -bfield_func_3x(double t, const double *xc, double* GKYL_RESTRICT fout, void *ctx) +bfield_func_3x(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) { - double x = xc[0], y = xc[1], z = xc[2]; - + double 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)); + fout[2] = params->B_m * (1.0 - ((params->R_m - 1.0) / params->R_m) * pow(cos(z), 2.0)); } void -phi_func_1x(double t, const double *xc, double* GKYL_RESTRICT fout, void *ctx) +phi_func_1x_zero(double t, const double *xc, double *GKYL_RESTRICT fout, void *ctx) { - double z = xc[0]; + fout[0] = 0.0; +} +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 inline void +corner_coords_range(int ndim, int corner, double *eta) { - double z = xc[0], vpar = xc[1], mu = xc[2]; - struct loss_cone_mask_test_ctx *params = ctx; + for (int d = 0; d < ndim; ++d) { + int bit = ndim - 1 - d; + eta[d] = ((corner >> bit) & 1) ? 1.0 : -1.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 struct gkyl_array* +mk_basis_at_corners(const struct gkyl_basis *basis) +{ + int ndim = basis->ndim; + int num_corners = 1 << ndim; + struct gkyl_array *out = gkyl_array_new(GKYL_DOUBLE, basis->num_basis, num_corners); + + double eta[GKYL_MAX_DIM] = { 0.0 }; + for (int corner = 0; corner < num_corners; ++corner) { + corner_coords_range(ndim, corner, eta); + basis->eval(eta, gkyl_array_fetch(out, corner)); + } + + return out; } -void -test_1x2v_gk(int poly_order, bool use_gpu) +static inline double +field_corner_val(const struct gkyl_array *arr, const struct gkyl_array *basis_at_corners, + int num_basis, long linidx, int corner) +{ + const double *arr_d = gkyl_array_cfetch(arr, linidx); + const double *basis_d = 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; +} + +static inline double +vel_corner_val(const struct gkyl_velocity_map *gvm, const int *vel_idx, int vd, int vel_corner, + int vdim) +{ + long linidx_vel = gkyl_range_idx(&gvm->local_ext_vel, vel_idx); + const double *vmap_d = gkyl_array_cfetch(gvm->vmap, linidx_vel); + + double vel_eta[GKYL_MAX_DIM] = { 0.0 }; + corner_coords_range(vdim, vel_corner, vel_eta); + double xcomp[1] = { vel_eta[vd] }; + + return gvm->vmap_basis->eval_expand(xcomp, vmap_d + vd * gvm->vmap_basis->num_basis); +} + +static inline int +corner_z_endpoint_side_ref(int cdim, int corner, int zdim) +{ + double eta[GKYL_MAX_DIM] = { 0.0 }; + corner_coords_range(cdim, corner, eta); + return eta[zdim] > 0.0 ? 1 : 0; +} + +static inline int +corner_with_z_side_ref(int cdim, int anchor_corner, int zdim, int z_side) +{ + int num_conf_corners = 1 << cdim; + + double eta_anchor[GKYL_MAX_DIM] = { 0.0 }; + corner_coords_range(cdim, anchor_corner, eta_anchor); + + for (int cand = 0; cand < num_conf_corners; ++cand) { + if (corner_z_endpoint_side_ref(cdim, cand, zdim) != z_side) { + continue; + } + + double eta_cand[GKYL_MAX_DIM] = { 0.0 }; + corner_coords_range(cdim, cand, eta_cand); + + bool same_transverse = true; + for (int d = 0; d < cdim; ++d) { + if (d == zdim) { + continue; + } + if (eta_cand[d] != eta_anchor[d]) { + same_transverse = false; + break; + } + } + + if (same_transverse) { + return cand; + } + } + + assert(false); + return anchor_corner; +} + +static void +escape_barriers_ref(const struct gkyl_array *phi, const struct gkyl_array *bmag, + const struct gkyl_range *conf_range, const struct gkyl_array *basis_at_corners_conf, + int cdim, int num_basis_conf, const int *base_idx, int target_z_cell, + int anchor_corner, int anchor_z_side, double mu, double charge, double *barrier_left, + double *barrier_right) +{ + int zdim = cdim - 1; + int z_upper_corner = corner_with_z_side_ref(cdim, anchor_corner, zdim, 1); + int z_lower_corner = corner_with_z_side_ref(cdim, anchor_corner, zdim, 0); + + int scan_idx[GKYL_MAX_DIM]; + 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 = field_corner_val(phi, basis_at_corners_conf, num_basis_conf, + linidx, z_lower_corner); + double phi_upper = field_corner_val(phi, basis_at_corners_conf, num_basis_conf, + linidx, z_upper_corner); + double bmag_lower = field_corner_val(bmag, basis_at_corners_conf, num_basis_conf, + linidx, z_lower_corner); + double bmag_upper = field_corner_val(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; + + if (iz < target_z_cell) { + *barrier_left = GKYL_MAX2(*barrier_left, GKYL_MAX2(u_lower, u_upper)); + } + else if (iz == target_z_cell) { + *barrier_left = GKYL_MAX2(*barrier_left, u_lower); + *barrier_right = GKYL_MAX2(*barrier_right, u_upper); + if (anchor_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)); + } + } +} + +static void +build_reference_mask(const struct gkyl_range *phase_range, const struct gkyl_range *conf_range, + const struct gkyl_basis *conf_basis, const struct gkyl_velocity_map *gvm, + const struct gkyl_array *bmag, const struct gkyl_array *phi, + double mass, double charge, struct gkyl_array *mask_ref) { + int cdim = conf_basis->ndim; + int pdim = phase_range->ndim; + int vdim = pdim - cdim; + int num_basis_conf = conf_basis->num_basis; + int num_phase_corners = 1 << pdim; + + struct gkyl_array *basis_at_corners_conf = mk_basis_at_corners(conf_basis); + + 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_DIM] = { 0 }; + int vel_idx[GKYL_MAX_DIM] = { 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]; + } + + bool cell_trapped = true; + for (int corner = 0; corner < num_phase_corners && cell_trapped; ++corner) { + int conf_corner = corner / (1 << vdim); + int vel_corner = corner % (1 << vdim); + + double vpar = vel_corner_val(gvm, vel_idx, 0, vel_corner, vdim); + double mu = vel_corner_val(gvm, vel_idx, 1, vel_corner, vdim); + + long linidx_conf = gkyl_range_idx(conf_range, conf_idx); + double bmag_curr = field_corner_val(bmag, basis_at_corners_conf, num_basis_conf, + linidx_conf, conf_corner); + double phi_curr = field_corner_val(phi, basis_at_corners_conf, num_basis_conf, + linidx_conf, conf_corner); + double h_curr = 0.5 * mass * vpar * vpar + mu * bmag_curr + charge * phi_curr; + + int zdim = cdim - 1; + int target_z_cell = conf_idx[zdim]; + int anchor_z_side = corner_z_endpoint_side_ref(cdim, conf_corner, zdim); + + double barrier_left, barrier_right; + escape_barriers_ref(phi, bmag, conf_range, basis_at_corners_conf, cdim, + num_basis_conf, conf_idx, target_z_cell, conf_corner, anchor_z_side, mu, charge, + &barrier_left, &barrier_right); + + 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; + } + gkyl_array_release(basis_at_corners_conf); +} + +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 = M_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 = M_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); + struct gkyl_array *phi_ho = mkarr(false, basis_conf.num_basis, local_ext_conf.volume); - gkyl_eval_on_nodes *evphi = gkyl_eval_on_nodes_new(&grid_conf, &basis_conf, 1, phi_func_1x, &ctx); + 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)); - } - - // 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; - if (use_gpu) { - bmag_max = gkyl_cu_malloc(sizeof(double)); - gkyl_cu_memcpy(bmag_max, bmag_max_ho, sizeof(double), GKYL_CU_MEMCPY_H2D); - } - else { - bmag_max = gkyl_malloc(sizeof(double)); - memcpy(bmag_max, bmag_max_ho, sizeof(double)); - } - - // 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); - } - else { - phi_m = gkyl_malloc(sizeof(double)); - memcpy(phi_m, phi_m_ho, sizeof(double)); - } - - // 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); - } - - // 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); - - // 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, + .conf_range = &local_conf, .vel_map = gvm, - .bmag = gk_geom->geo_int.bmag, - .bmag_max = bmag_max, - .bmag_max_loc = bmag_max_loc, + .use_gpu = use_gpu, .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, }; - struct gkyl_loss_cone_mask_gyrokinetic *proj_mask = gkyl_loss_cone_mask_gyrokinetic_inew( &inp_proj ); + 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); + 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 = mkarr(false, 1, local_ext.volume); + struct gkyl_array *mask_ref = mkarr(false, 1, local_ext.volume); + gkyl_loss_cone_mask_gyrokinetic_advance(proj_mask, &local, &local_conf, + gk_geom->geo_corn.bmag, phi, 0, 0, mask); gkyl_array_copy(mask_ho, mask); - // 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_array *bmag_ho = mkarr(false, basis_conf.num_basis, local_ext_conf.volume); + gkyl_array_copy(bmag_ho, gk_geom->geo_corn.bmag); + + { + char f_bmag[256], f_phi[256], f_mask[256]; + const char *suffix_dev = use_gpu ? "dev" : "ho"; + const char *suffix_phi = use_nonzero_phi ? "nonzero_phi" : "zero_phi"; + const struct gkyl_range arr_range_conf = local_conf; + const struct gkyl_range arr_range = local; + + snprintf(f_bmag, sizeof(f_bmag), "ctest_loss_cone_mask_%s_%s_bmag.gkyl", suffix_dev, + suffix_phi); + snprintf(f_phi, sizeof(f_phi), "ctest_loss_cone_mask_%s_%s_phi.gkyl", suffix_dev, + suffix_phi); + snprintf(f_mask, sizeof(f_mask), "ctest_loss_cone_mask_%s_%s_mask.gkyl", suffix_dev, + suffix_phi); + + gkyl_grid_sub_array_write(&grid_conf, &arr_range_conf, 0, bmag_ho, f_bmag); + gkyl_grid_sub_array_write(&grid_conf, &arr_range_conf, 0, phi_ho, f_phi); + gkyl_grid_sub_array_write(&grid, &arr_range, 0, mask_ho, f_mask); } -// // 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_boundary = test->lower_boundary, + .upper_boundary = test->upper_boundary, + }); + + const struct gkyl_array *wall_lo = test->lower_boundary == GKYL_LOSS_CONE_BC_SHEATH + ? phi_wall_lo : 0; + const struct gkyl_array *wall_up = test->upper_boundary == GKYL_LOSS_CONE_BC_SHEATH + ? 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->lower_boundary); + double barrier_up = constant_boundary_energy(test->charge, test->phi_plasma, + test->phi_wall_up, test->upper_boundary); + + 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(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_velocity_map_release(gvm); +} + +void +test_1x2v_p1_gk_ho(void) +{ + run_case_1x2v(1, false, false); +} + +void +test_1x2v_p1_nonzero_phi_gk_ho(void) +{ + run_case_1x2v(1, false, true); +} + +void +test_1x2v_p1_constant_boundaries_gk_ho(void) +{ + run_constant_boundary_cases(false); +} #ifdef GKYL_HAVE_CUDA -void test_1x2v_p1_gk_dev() { test_1x2v_gk(1, true); } +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..9f3a9bdefd --- /dev/null +++ b/gyrokinetic/unit/mctest_loss_cone_mask_gyrokinetic.c @@ -0,0 +1,230 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef GKYL_HAVE_MPI +#include +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#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) +{ + 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 = M_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_position_map *pmap = gkyl_position_map_null_new(); + 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, + .conf_range = &conf_global, + .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, + .conf_range = &conf_global, + .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_position_map_release(pmap); + 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..1094aa8b79 100644 --- a/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic.h +++ b/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic.h @@ -10,70 +10,67 @@ // 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. +/** Boundary behavior used by the orbit classifier. */ +enum gkyl_loss_cone_boundary_type { + GKYL_LOSS_CONE_BC_OPEN = 0, // A particle that reaches the domain edge is lost. + GKYL_LOSS_CONE_BC_SHEATH, // The material-wall potential may reflect the particle. + GKYL_LOSS_CONE_BC_CLOSED, // No particle can be lost through this edge. +}; // 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_loss_cone_boundary_type lower_boundary; // Lower parallel-boundary behavior. + enum gkyl_loss_cone_boundary_type upper_boundary; // Upper parallel-boundary behavior. }; /** * 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. For sheath boundaries the material + * wall is a virtual endpoint outside the plasma domain, so + * EB_L = max(max_{s in [z_L,z]} U(s,mu), mu*B(z_L)+q*phi_wall_L) + * EB_R = max(max_{s in [z,z_R]} U(s,mu), mu*B(z_R)+q*phi_wall_R) + * with U = mu*B + q*phi. Open boundaries omit the virtual endpoint and + * closed boundaries cannot be escaped. + * A node is trapped if H < EB. * * @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 on phase-space cell nodes. + * + * 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 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 at the plasma/sheath entrance. + * @param phi_wall_lo Lower material-wall potential. Required for a lower sheath boundary. + * @param phi_wall_up Upper material-wall potential. Required for an upper sheath boundary. + * @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..43ad1797cc 100644 --- a/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic_priv.h +++ b/gyrokinetic/zero/gkyl_loss_cone_mask_gyrokinetic_priv.h @@ -1,6 +1,7 @@ // Private header: not for direct use #pragma once +#include #include #include @@ -8,105 +9,220 @@ #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) +nodal_coords(int ndim, int node, double *x) { - for (int d=0; d 0.0 ? 1 : 0; +} + +GKYL_CU_DH +static inline int +conf_node_with_matching_perpendicular_coords(int cdim, int anchor_node, int zdim, + int z_endpoint_index) +{ + int num_nodes_conf = (int)pow(2.0, (double)cdim); + + double eta_anchor[GKYL_MAX_DIM] = { 0.0 }; + nodal_coords(cdim, anchor_node, eta_anchor); + + for (int cand = 0; cand < num_nodes_conf; ++cand) { + if (conf_node_z_endpoint_index(cdim, cand, zdim) != z_endpoint_index) { + continue; + } + + double eta_cand[GKYL_MAX_DIM] = { 0.0 }; + nodal_coords(cdim, cand, eta_cand); + + bool same_transverse = true; + for (int d = 0; d < cdim; ++d) { + if (d == zdim) { + continue; + } + if (eta_cand[d] != eta_anchor[d]) { + same_transverse = false; + break; + } + } + + if (same_transverse) { + return cand; + } + } + + return anchor_node; +} + +GKYL_CU_DH +static inline double +field_node_val(const struct gkyl_array *arr, const struct gkyl_array *basis_at_nodes, + int num_basis, long linidx, int node) +{ + const double *arr_d = (const double *)gkyl_array_cfetch(arr, linidx); + const double *basis_d = (const double *)gkyl_array_cfetch(basis_at_nodes, node); + + double val = 0.0; + for (int k = 0; k < num_basis; ++k) { + val += arr_d[k] * basis_d[k]; + } + + return val; +} + +GKYL_CU_DH +static inline bool +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; +} + +GKYL_CU_DH +static inline void +escape_barriers(int cdim, int num_basis_conf, const struct gkyl_range *conf_range, + const struct gkyl_array *basis_at_nodes_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_node, double mu, double charge, + enum gkyl_loss_cone_boundary_type lower_boundary, + enum gkyl_loss_cone_boundary_type upper_boundary, + double *barrier_left, double *barrier_right) +{ + int zdim = cdim - 1; + + int z_endpoint_index = conf_node_z_endpoint_index(cdim, anchor_conf_node, zdim); + int z_upper_node = conf_node_with_matching_perpendicular_coords(cdim, anchor_conf_node, + zdim, 1); + int z_lower_node = conf_node_with_matching_perpendicular_coords(cdim, anchor_conf_node, + zdim, 0); + + int scan_idx[GKYL_MAX_DIM]; + 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 = field_node_val(phi, basis_at_nodes_conf, num_basis_conf, + linidx, z_lower_node); + double phi_upper = field_node_val(phi, basis_at_nodes_conf, num_basis_conf, + linidx, z_upper_node); + double bmag_lower = field_node_val(bmag, basis_at_nodes_conf, num_basis_conf, + linidx, z_lower_node); + double bmag_upper = field_node_val(bmag, basis_at_nodes_conf, num_basis_conf, + linidx, z_upper_node); + 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 node 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_endpoint_index == 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_boundary == GKYL_LOSS_CONE_BC_CLOSED) { + *barrier_left = DBL_MAX; + } + else if (lower_boundary == GKYL_LOSS_CONE_BC_SHEATH) { + scan_idx[zdim] = conf_range->lower[zdim]; + long linidx = gkyl_range_idx(conf_range, scan_idx); + double bmag_wall = field_node_val(bmag, basis_at_nodes_conf, num_basis_conf, + linidx, z_lower_node); + double phi_wall = field_node_val(phi_wall_lo, basis_at_nodes_conf, num_basis_conf, + linidx, z_lower_node); + *barrier_left = GKYL_MAX2(*barrier_left, mu * bmag_wall + charge * phi_wall); + } + + if (upper_boundary == GKYL_LOSS_CONE_BC_CLOSED) { + *barrier_right = DBL_MAX; + } + else if (upper_boundary == GKYL_LOSS_CONE_BC_SHEATH) { + scan_idx[zdim] = conf_range->upper[zdim]; + long linidx = gkyl_range_idx(conf_range, scan_idx); + double bmag_wall = field_node_val(bmag, basis_at_nodes_conf, num_basis_conf, + linidx, z_upper_node); + double phi_wall = field_node_val(phi_wall_up, basis_at_nodes_conf, num_basis_conf, + linidx, z_upper_node); + *barrier_right = GKYL_MAX2(*barrier_right, mu * bmag_wall + charge * phi_wall); + } +} + #ifdef GKYL_HAVE_CUDA -/** - * Obtain bmag_max-bmag at conf-space quadrature nodes and store it in up->Dbmag_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. - */ -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); - -/** - * 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. - */ -void -gkyl_loss_cone_mask_gyrokinetic_advance_cu(gkyl_loss_cone_mask_gyrokinetic *up, +#ifdef __cplusplus +extern "C" { +#endif + +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); + +#ifdef __cplusplus +} +#endif #endif diff --git a/gyrokinetic/zero/gkyl_position_map_priv.h b/gyrokinetic/zero/gkyl_position_map_priv.h index ab3b3ae3c5..f0b40142ac 100644 --- a/gyrokinetic/zero/gkyl_position_map_priv.h +++ b/gyrokinetic/zero/gkyl_position_map_priv.h @@ -276,13 +276,13 @@ 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); } /** @@ -314,15 +314,24 @@ 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++){ + 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){ + // Near-zero derivative: B is locally flat here, record as a minimum. + // Use continue so this is mutually exclusive with the sign-change checks below. + if (fabs(dbmag_vals[i]) < 1e-10) { + 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 && fabs(dbmag_vals[i-1]) >= 1e-10){ if (bmag_vals[i] < bmag_vals[i-1]) { theta_extrema[extrema] = theta; @@ -337,8 +346,8 @@ 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 && fabs(dbmag_vals[i-1]) >= 1e-10){ if (bmag_vals[i] > bmag_vals[i-1]) { theta_extrema[extrema] = theta; @@ -375,30 +384,53 @@ find_B_field_extrema(struct gkyl_position_map *gpm) // Left edge if (bmag_extrema[0] > bmag_extrema[1]) - { gpm->constB_ctx->min_or_max[0] = 1; } // Maximum + { + gpm->constB_ctx->min_or_max[0] = 1; // Maximum + } else if (bmag_extrema[0] < bmag_extrema[1]) - { gpm->constB_ctx->min_or_max[0] = 0; } // Minimum + { + gpm->constB_ctx->min_or_max[0] = 0; // Minimum + } else - { printf("Error: Extrema is not an extrema. Position_map optimization failed\n"); } + { + printf("Error: Extrema[0] is not an extrema (bmag[0]=%.6g == bmag[1]=%.6g). " + "Position_map optimization failed\n", bmag_extrema[0], bmag_extrema[1]); + } // Middle points for (int i = 1; i < extrema - 1; i++) { if (bmag_extrema[i] > bmag_extrema[i-1] && bmag_extrema[i] > bmag_extrema[i+1]) - { gpm->constB_ctx->min_or_max[i] = 1; } // Maximum + { + gpm->constB_ctx->min_or_max[i] = 1; // Maximum + } else if (bmag_extrema[i] < bmag_extrema[i-1] && bmag_extrema[i] < bmag_extrema[i+1]) - { gpm->constB_ctx->min_or_max[i] = 0; } // Minimum + { + gpm->constB_ctx->min_or_max[i] = 0; // Minimum + } else - { printf("Error: Extrema is not an extrema. Position_map optimization failed\n"); } + { + printf("Error: Extrema[%d] is not an extrema (bmag[%d-1]=%.6g, bmag[%d]=%.6g, bmag[%d+1]=%.6g). " + "Position_map optimization failed\n", + i, i, bmag_extrema[i-1], i, bmag_extrema[i], i, bmag_extrema[i+1]); + } } // Right edge if (bmag_extrema[extrema-1] > bmag_extrema[extrema-2]) - { gpm->constB_ctx->min_or_max[extrema-1] = 1; } // Maximum + { + 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 - { printf("Error: Extrema is not an extrema. Position_map optimization failed\n"); } + { + gpm->constB_ctx->min_or_max[extrema-1] = 0; // Minimum + } + else + { + printf("Error: Extrema[%d] (right edge) is not an extrema (bmag[%d-1]=%.6g, bmag[%d]=%.6g). " + "Position_map optimization failed\n", + extrema-1, extrema-1, bmag_extrema[extrema-2], extrema-1, bmag_extrema[extrema-1]); + } // Free mallocs gkyl_free(bmag_vals); @@ -454,7 +486,7 @@ refine_B_field_extrema(struct gkyl_position_map *gpm) else if (bmag_cent < bmag_left && bmag_cent < bmag_right) { is_maximum = false; } // Local minima else - { printf("Error: Extrema is not an extrema. Position_map optimization failed\n"); + { // printf("Error: Extrema is not an extrema. Position_map optimization failed\n"); break; } @@ -655,7 +687,7 @@ position_map_constB_z_numeric(double t, const double *xn, double *fout, void *ct return; } else { - fprintf(stderr, "Warning: Unexpected interval evaluation state in position_map_constB_z_numeric. Using theta directly.\n"); + // fprintf(stderr, "Warning: Unexpected interval evaluation state in position_map_constB_z_numeric. Using theta directly.\n"); fout[0] = theta; return; } @@ -666,14 +698,16 @@ position_map_constB_z_numeric(double t, const double *xn, double *fout, void *ct interval_lower, interval_upper, interval_lower_eval, interval_upper_eval, 10, 1e-6); double Theta = res.res; fout[0] = Theta*gpm->constB_ctx->map_strength + theta*(1-gpm->constB_ctx->map_strength); - bool enable_limits_min_B = gpm->constB_ctx->enable_maximum_slope_limits_at_min_B; bool enable_limits_max_B = gpm->constB_ctx->enable_maximum_slope_limits_at_max_B; 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 +727,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; - } - - 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; - } + // 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; + + // 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) || diff --git a/gyrokinetic/zero/loss_cone_mask_gyrokinetic.c b/gyrokinetic/zero/loss_cone_mask_gyrokinetic.c index 8eee0f3ef6..9f19a830f7 100644 --- a/gyrokinetic/zero/loss_cone_mask_gyrokinetic.c +++ b/gyrokinetic/zero/loss_cone_mask_gyrokinetic.c @@ -1,485 +1,166 @@ -#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 left and right escape barriers. A +// sheath wall is represented by a virtual endpoint with potential phi_wall; +// this is distinct from phi at the plasma/sheath entrance. -// 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) +static struct gkyl_array* +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])); + int num_nodes = (int)pow(2.0, (double)ndim); + struct gkyl_array *basis_at_nodes_ho = gkyl_array_new(GKYL_DOUBLE, basis->num_basis, + num_nodes); + *basis_at_nodes = mkarr(basis->num_basis, num_nodes, use_gpu); - 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); + double eta[GKYL_MAX_DIM] = { 0.0 }; + for (int node = 0; node < num_nodes; ++node) { + nodal_coords(ndim, node, eta); + basis->eval(eta, gkyl_array_fetch(basis_at_nodes_ho, node)); } - 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 num_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) -{ - // 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; kgrid_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_boundary = inp->lower_boundary; + up->upper_boundary = inp->upper_boundary; - // 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); - - // 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); - } -#endif + assert(up->lower_boundary >= GKYL_LOSS_CONE_BC_OPEN && + up->lower_boundary <= GKYL_LOSS_CONE_BC_CLOSED); + assert(up->upper_boundary >= GKYL_LOSS_CONE_BC_OPEN && + up->upper_boundary <= GKYL_LOSS_CONE_BC_CLOSED); - // 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); + up->cdim = inp->conf_basis->ndim; + up->num_basis_conf = inp->conf_basis->num_basis; + up->num_nodes_conf = (int)pow(2.0, (double)up->cdim); - gkyl_array_clear(up->Dbmag_quad, 0.0); - gkyl_loss_cone_mask_gyrokinetic_Dbmag_quad(up, inp->conf_range, inp->bmag, inp->bmag_max); + init_node_values(up->cdim, inp->conf_basis, &up->basis_at_nodes_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_boundary != GKYL_LOSS_CONE_BC_SHEATH || phi_wall_lo); + assert(up->upper_boundary != GKYL_LOSS_CONE_BC_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; 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; + int num_phase_nodes = (int)pow(2.0, (double)pdim); + int num_vel_nodes = (int)pow(2.0, (double)vdim); + + // 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_DIM] = { 0 }; + int vel_idx[GKYL_MAX_DIM] = { 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; + 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); + + // At each phase space cell, loop over all nodes and check if they are trapped or not. If any node is not trapped, the whole cell is not trapped, so we can break out of the loop early in that case. + bool cell_trapped = true; + for (int node = 0; node < num_phase_nodes && cell_trapped; ++node) { + int conf_node = node / num_vel_nodes; + int vel_node = node % num_vel_nodes; + + // Evaluate velocity coordinates at the node + double xmu[GKYL_MAX_DIM] = { 0.0 }; + for (int vd = 0; vd < vdim; ++vd) { + double vel_eta[GKYL_MAX_DIM] = { 0.0 }; + nodal_coords(vdim, vel_node, vel_eta); + double xcomp[1] = { vel_eta[vd] }; + xmu[cdim + vd] = gvm->vmap_basis->eval_expand(xcomp, + vmap_d + vd * gvm->vmap_basis->num_basis); } - // 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)); + double mu = xmu[cdim + 1]; + double vpar = xmu[cdim]; + + // Evaluate Hamiltonian at the node + long linidx_conf = gkyl_range_idx(conf_range, conf_idx); + double bmag_curr = field_node_val(bmag, up->basis_at_nodes_conf, num_basis_conf, + linidx_conf, conf_node); + double phi_curr = field_node_val(phi, up->basis_at_nodes_conf, num_basis_conf, + linidx_conf, conf_node); + double kinetic_energy = 0.5 * up->mass * vpar * vpar; + double magnetic_energy = mu * bmag_curr; + double electric_energy = up->charge * phi_curr; + + // Determine escape barriers at the node + int zdim = cdim - 1; + double barrier_left, barrier_right; + escape_barriers(cdim, num_basis_conf, conf_range, up->basis_at_nodes_conf, + phi, bmag, phi_wall_lo, phi_wall_up, conf_idx, conf_idx[zdim], conf_node, + mu, up->charge, up->lower_boundary, up->upper_boundary, + &barrier_left, &barrier_right); + + // Equality means that the particle reaches the wall with zero parallel + // kinetic energy, matching the sheath boundary's absorbing cutoff. + cell_trapped = hamiltonian_below_barrier(kinetic_energy, magnetic_energy, + electric_energy, barrier_left) && 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_nodes_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..9500bd4f29 100644 --- a/gyrokinetic/zero/loss_cone_mask_gyrokinetic_cu.cu +++ b/gyrokinetic/zero/loss_cone_mask_gyrokinetic_cu.cu @@ -1,277 +1,120 @@ /* -*- c++ -*- */ -#include -#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; n } -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) -{ - int nblocks = conf_range->nblocks, 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, + int num_phase_nodes, int num_vel_nodes, double mass, double charge, + struct gkyl_range phase_range, struct gkyl_range conf_range, + const struct gkyl_array *basis_at_nodes_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_loss_cone_boundary_type lower_boundary, + enum gkyl_loss_cone_boundary_type upper_boundary, + 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); + int pdim = phase_range.ndim; + int vdim = pdim - cdim; - 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 phase_idx[GKYL_MAX_DIM]; + int conf_idx[GKYL_MAX_DIM] = { 0 }; + int vel_idx[GKYL_MAX_DIM] = { 0 }; - 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); + for (unsigned long tid = threadIdx.x + blockIdx.x * blockDim.x; + tid < phase_range.volume; tid += blockDim.x * gridDim.x) { - mask_d[0] = 1.0; // In this case the mask has ncomp=1. + gkyl_sub_range_inv_idx(&phase_range, tid, phase_idx); - 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]; + } + for (int d = 0; d < vdim; ++d) { + vel_idx[d] = phase_idx[cdim + 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); + 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); - int cqidx = p2c_qidx[linc2]; - for (int d = cdim; d < pdim; d++) vidx[d-cdim] = pidx[d]; + bool cell_trapped = true; + for (int node = 0; node < num_phase_nodes && cell_trapped; ++node) { + int conf_node = node / num_vel_nodes; + int vel_node = node % num_vel_nodes; - 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); + double xmu[GKYL_MAX_DIM] = { 0.0 }; + double vel_eta[GKYL_MAX_DIM] = { 0.0 }; + nodal_coords(vdim, vel_node, vel_eta); - // 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); + for (int vd = 0; vd < vdim; ++vd) { + double xcomp[1] = { vel_eta[vd] }; + xmu[cdim + vd] = gvm->vmap_basis->eval_expand(xcomp, + vmap_d + vd * gvm->vmap_basis->num_basis); + } - // 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); + double mu = xmu[cdim + 1]; + double vpar = xmu[cdim]; + + long linidx_conf = gkyl_range_idx(&conf_range, conf_idx); + double bmag_curr = field_node_val(bmag, basis_at_nodes_conf, num_basis_conf, + linidx_conf, conf_node); + double phi_curr = field_node_val(phi, basis_at_nodes_conf, num_basis_conf, + linidx_conf, conf_node); + 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; + escape_barriers(cdim, num_basis_conf, &conf_range, basis_at_nodes_conf, + phi, bmag, phi_wall_lo, phi_wall_up, conf_idx, conf_idx[zdim], conf_node, + mu, charge, lower_boundary, upper_boundary, + &barrier_left, &barrier_right); + + cell_trapped = hamiltonian_below_barrier(kinetic_energy, magnetic_energy, + electric_energy, barrier_left) && hamiltonian_below_barrier(kinetic_energy, + magnetic_energy, electric_energy, barrier_right); } - // 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]); - - 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 +extern "C" 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); - - 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 pdim = phase_range->ndim; + int vdim = pdim - up->cdim; + int num_phase_nodes = (int)pow(2.0, (double)pdim); + int num_vel_nodes = (int)pow(2.0, (double)vdim); 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, num_phase_nodes, num_vel_nodes, + up->mass, up->charge, *phase_range, *conf_range, + up->basis_at_nodes_conf->on_dev, bmag->on_dev, phi->on_dev, + phi_wall_lo_dev, phi_wall_up_dev, up->lower_boundary, up->upper_boundary, + gvm->on_dev, mask_out->on_dev); }