From c915f5987938fe93fa0a0319d37d09cbd2ffe6c0 Mon Sep 17 00:00:00 2001 From: manauref Date: Sat, 9 May 2026 10:33:19 -0600 Subject: [PATCH 01/30] Interpolate potential to a 2x higher resolution grid and apply TS BC there, then coarsen back to the original grid as an attempt to deal with aliasing. There are many inefficiencies right now, like interpolatig in the whole volume and declaring whole-volume buffers, rather than ones just 1-cell-in-z wide, but it's just a proof of principle right now. Reg test runs on CPU, not yet tested on GPU. --- gyrokinetic/apps/gk_field_2x3x.c | 151 ++++++++++++++++------- gyrokinetic/apps/gkyl_gyrokinetic_priv.h | 7 ++ 2 files changed, 116 insertions(+), 42 deletions(-) diff --git a/gyrokinetic/apps/gk_field_2x3x.c b/gyrokinetic/apps/gk_field_2x3x.c index 2bc65a55a4..8db4fcd923 100644 --- a/gyrokinetic/apps/gk_field_2x3x.c +++ b/gyrokinetic/apps/gk_field_2x3x.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -117,6 +118,32 @@ gk_field_fem_projection_par_phi_ts_2x(gkyl_gyrokinetic_app *app, struct gk_field gkyl_array_copy_range_to_range(arr_fem, field->phi_fem, &app->local, &field->global_sub_range); } +static void +gk_field_apply_ts_bc_interp(gkyl_gyrokinetic_app *app, struct gk_field *gkf, + struct gkyl_array *fin, struct gkyl_array *fout) +{ + // Apply twistshift BC to the quantity `fin`, on a higher resolution grid, and place the output in `fout` + + // Prolong (interpolate up to higher resolution). + gkyl_dg_interpolate_advance(gkf->bc_ts_prolong, fin, gkf->bc_ts_buffer_fine); + + // Coppy upper skin to lower ghost and apply TSBC + gkyl_array_copy_range_to_range(gkf->bc_ts_buffer_fine, gkf->bc_ts_buffer_fine, + &gkf->bc_ts_global_lower_ghost_par, &gkf->bc_ts_global_upper_skin_par); + gkyl_bc_twistshift_advance(gkf->bc_ts_lo, gkf->bc_ts_buffer_fine, gkf->bc_ts_buffer_fine); + + // Copy TS-ed ghost into skin so it gets coarsened. + gkyl_array_copy_range_to_range(gkf->bc_ts_buffer_fine, gkf->bc_ts_buffer_fine, + &gkf->bc_ts_global_lower_skin_par, &gkf->bc_ts_global_lower_ghost_par); + // Restrict (coarsen to lower resolution). + gkyl_dg_interpolate_advance(gkf->bc_ts_coarsen, gkf->bc_ts_buffer_fine, gkf->bc_ts_buffer_coar); + + // Copy skin back into ghost. + int par_dir = app->cdim-1; // Parallel direction index. + gkyl_array_copy_range_to_range(fout, gkf->bc_ts_buffer_coar, + &app->global_lower_ghost[par_dir], &app->global_lower_skin[par_dir]); +} + static void gk_field_fem_projection_par_phi_ts_3x(gkyl_gyrokinetic_app *app, struct gk_field *field, struct gkyl_array *arr_dg, struct gkyl_array *arr_fem) @@ -128,12 +155,13 @@ gk_field_fem_projection_par_phi_ts_3x(gkyl_gyrokinetic_app *app, struct gk_field // Gather the DG array into a global (in z) array. gkyl_comm_array_allgather(app->comm, &app->local, &app->global, arr_dg, field->rho_c_global_dg); - // Apply TS BC in the core lower parallel boundary, and - // fill core upper parallel boundary ghost with skin boundary value. - int par_dir = app->cdim-1; // Parallel direction index. - gkyl_array_copy_range_to_range(field->rho_c_global_dg, field->rho_c_global_dg, - &app->global_lower_ghost[par_dir], &app->global_upper_skin[par_dir]); - gkyl_bc_twistshift_advance(field->bc_ts_lo, field->rho_c_global_dg, field->rho_c_global_dg); + // Apply TS BC in the lower parallel boundary. + gk_field_apply_ts_bc_interp(app, field, field->rho_c_global_dg, field->rho_c_global_dg); +// int par_dir = app->cdim-1; // Parallel direction index. +// gkyl_array_copy_range_to_range(field->rho_c_global_dg, field->rho_c_global_dg, +// &app->global_lower_ghost[par_dir], &app->global_upper_skin[par_dir]); +// gkyl_bc_twistshift_advance(field->bc_ts_lo, field->rho_c_global_dg, field->rho_c_global_dg); + // Fill upper parallel boundary ghost with skin boundary value. gkyl_bc_basic_gyrokinetic_advance(field->gfss_bc_op_core_up, field->bc_buffer, field->rho_c_global_dg); // Smooth the the DG array. @@ -296,7 +324,7 @@ gk_field_2x3x_fill_fem_parproj_bias_lines(struct gkyl_gyrokinetic_app *app, stru } static void -gk_field_2x3x_add_TS_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field *f, struct gkyl_poisson_bc *poisson_bcs) +gk_field_2x3x_add_TS_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field *gkf, struct gkyl_poisson_bc *poisson_bcs) { // Allocation ranges and updaters for TS field solve. @@ -308,19 +336,57 @@ gk_field_2x3x_add_TS_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field fem_parproj_bc_rho_core = GKYL_FEM_PARPROJ_PERIODIC; fem_parproj_bc_phi_core = GKYL_FEM_PARPROJ_PERIODIC; - f->fem_projection_par_rho_func = gk_field_fem_projection_par_rho_ts_2x; - f->fem_projection_par_phi_func = gk_field_fem_projection_par_phi_ts_2x; + gkf->fem_projection_par_rho_func = gk_field_fem_projection_par_rho_ts_2x; + gkf->fem_projection_par_phi_func = gk_field_fem_projection_par_phi_ts_2x; } else if (app->cdim == 3) { - // Here fem_parproj_bc_rho is not actually relevant because we don't use f->fem_parproj_rho. + // Here fem_parproj_bc_rho is not actually relevant because we don't use gkf->fem_parproj_rho. fem_parproj_bc_rho_core = 0; fem_parproj_bc_phi_core = GKYL_FEM_PARPROJ_DIRICHLET_GHOST; - f->fem_projection_par_rho_func = gk_field_fem_projection_par; - f->fem_projection_par_phi_func = gk_field_fem_projection_par_phi_ts_3x; + gkf->fem_projection_par_rho_func = gk_field_fem_projection_par; + gkf->fem_projection_par_phi_func = gk_field_fem_projection_par_phi_ts_3x; - // Take the TS function from the parallel BC of the first species. + // Initialize an interpolation operator on a higher resolution grid, with + // twice as many cells along x and y. + int ts_cells[GKYL_MAX_CDIM]; + for (int d=0; dcdim-1; d++) { + ts_cells[d] = 2*app->grid.cells[d]; + } + ts_cells[app->cdim-1] = app->grid.cells[app->cdim-1]; + gkyl_rect_grid_init(&gkf->bc_ts_grid, app->cdim, app->grid.lower, app->grid.upper, ts_cells); + + int ghost_do[GKYL_MAX_CDIM]; + for (int d=0; dcdim; d++) ghost_do[d] = 1; + gkyl_create_grid_ranges(&gkf->bc_ts_grid, ghost_do, &gkf->bc_ts_global_ext, &gkf->bc_ts_global); + + // Skin and ghost ranges for configuration space fields. + int num_ghost[] = {1, 1, 1}; int par_dir = app->cdim-1; // Parallel direction index. + gkyl_skin_ghost_ranges(&gkf->bc_ts_global_lower_skin_par, &gkf->bc_ts_global_lower_ghost_par, + par_dir, GKYL_LOWER_EDGE, &gkf->bc_ts_global_ext, num_ghost); + gkyl_skin_ghost_ranges(&gkf->bc_ts_global_upper_skin_par, &gkf->bc_ts_global_upper_ghost_par, + par_dir, GKYL_UPPER_EDGE, &gkf->bc_ts_global_ext, num_ghost); + + gkf->bc_ts_prolong = gkyl_dg_interpolate_new(app->cdim, &app->basis, + &app->grid, &gkf->bc_ts_grid, &app->global_ext, &gkf->bc_ts_global_ext, ghost_do, app->use_gpu); + gkf->bc_ts_coarsen = gkyl_dg_interpolate_new(app->cdim, &app->basis, + &gkf->bc_ts_grid, &app->grid, &gkf->bc_ts_global_ext, &app->global_ext, ghost_do, app->use_gpu); + + gkf->bc_ts_buffer_fine = mkarr(app->use_gpu, app->basis.num_basis, gkf->bc_ts_global_ext.volume); + gkf->bc_ts_buffer_coar = mkarr(app->use_gpu, app->basis.num_basis, app->global_ext.volume); + + // Create a global extended in the BC dir. + int lower_bcdir_ext[app->cdim], upper_bcdir_ext[app->cdim]; + for (int i=0; icdim; i++) { + lower_bcdir_ext[i] = gkf->bc_ts_global.lower[i]; + upper_bcdir_ext[i] = gkf->bc_ts_global.upper[i]; + } + lower_bcdir_ext[par_dir] = gkf->bc_ts_global_ext.lower[par_dir]; + upper_bcdir_ext[par_dir] = gkf->bc_ts_global_ext.upper[par_dir]; + gkyl_sub_range_init(&gkf->bc_ts_global_par_ext, &gkf->bc_ts_global_ext, lower_bcdir_ext, upper_bcdir_ext); + + // Take the TS function from the parallel BC of the first species. struct gk_species *gks = &app->species[0]; const struct gkyl_gyrokinetic_bc *par_lower_bc; for (int i = 0; i < 2*app->cdim; i++) { @@ -333,48 +399,47 @@ gk_field_2x3x_add_TS_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field } // TS BC updater for up to low TS for the lower edge. This sets ghost_L = T_LU(ghost_L). - int ghost[] = {1, 1, 1}; struct gkyl_bc_twistshift_inp T_LU_lo = { .bc_dir = par_dir, .shift_dir = 1, // y shift. .shear_dir = 0, // shift varies with x. .edge = GKYL_LOWER_EDGE, .cdim = app->cdim, - .bcdir_ext_update_r = app->global_par_ext, - .num_ghost = ghost, // one ghost per config direction + .bcdir_ext_update_r = gkf->bc_ts_global_par_ext, + .num_ghost = num_ghost, // one ghost per config direction .basis = app->basis, - .grid = app->grid, + .grid = gkf->bc_ts_grid, .shift_func = par_lower_bc->aux_profile, .shift_func_ctx = par_lower_bc->aux_ctx, .use_gpu = app->use_gpu, }; - f->bc_ts_lo = gkyl_bc_twistshift_new(&T_LU_lo); + gkf->bc_ts_lo = gkyl_bc_twistshift_new(&T_LU_lo); long buff_sz = app->global_lower_ghost[par_dir].volume; - f->bc_buffer = mkarr(app->use_gpu, app->basis.num_basis, buff_sz); + gkf->bc_buffer = mkarr(app->use_gpu, app->basis.num_basis, buff_sz); - f->gfss_bc_op_core_up = gkyl_bc_basic_gyrokinetic_new(par_dir, GKYL_UPPER_EDGE, GKYL_BC_GK_FIELD_BOUNDARY_VALUE, + gkf->gfss_bc_op_core_up = gkyl_bc_basic_gyrokinetic_new(par_dir, GKYL_UPPER_EDGE, GKYL_BC_GK_FIELD_BOUNDARY_VALUE, app->basis_on_dev, &app->global_upper_skin[par_dir], &app->global_upper_ghost[par_dir], app->basis.num_basis, app->cdim, app->use_gpu); // Write the discrete shift to file. - gk_field_3x_write_twistshift(app, f); + gk_field_3x_write_twistshift(app, gkf); } // Parallel smoother for the charge density. - f->fem_parproj_rho_core = gkyl_fem_parproj_new(&app->global, &app->grid, &app->basis, + gkf->fem_parproj_rho_core = gkyl_fem_parproj_new(&app->global, &app->grid, &app->basis, fem_parproj_bc_rho_core, 0, 0, 0, app->use_gpu); // Fill bias line list for fem_parproj_phi. - gk_field_2x3x_fill_fem_parproj_bias_lines(app, f, poisson_bcs); + gk_field_2x3x_fill_fem_parproj_bias_lines(app, gkf, poisson_bcs); // Parallel smoother for the potential. - f->fem_parproj_phi_core = gkyl_fem_parproj_new(&app->global, &app->grid, &app->basis, - fem_parproj_bc_phi_core, &f->fem_parproj_bias_line_list, 0, 0, app->use_gpu); + gkf->fem_parproj_phi_core = gkyl_fem_parproj_new(&app->global, &app->grid, &app->basis, + fem_parproj_bc_phi_core, &gkf->fem_parproj_bias_line_list, 0, 0, app->use_gpu); } static void -gk_field_2x3x_add_IWL_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field *f, struct gkyl_poisson_bc *poisson_bcs) +gk_field_2x3x_add_IWL_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field *gkf, struct gkyl_poisson_bc *poisson_bcs) { // Allocation ranges and updaters for IWL field solve. @@ -388,18 +453,18 @@ gk_field_2x3x_add_IWL_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field fem_parproj_bc_phi_core = GKYL_FEM_PARPROJ_PERIODIC; fem_parproj_bc_phi_sol = GKYL_FEM_PARPROJ_NONE; - f->fem_projection_par_rho_func = gk_field_fem_projection_par_rho_iwl_2x; - f->fem_projection_par_phi_func = gk_field_fem_projection_par_phi_iwl_2x; + gkf->fem_projection_par_rho_func = gk_field_fem_projection_par_rho_iwl_2x; + gkf->fem_projection_par_phi_func = gk_field_fem_projection_par_phi_iwl_2x; } else if (app->cdim == 3) { - // Here fem_parproj_bc_rho is not actually relevant because we don't use f->fem_parproj_rho. + // Here fem_parproj_bc_rho is not actually relevant because we don't use gkf->fem_parproj_rho. fem_parproj_bc_rho_core = 0; fem_parproj_bc_rho_sol = 0; fem_parproj_bc_phi_core = GKYL_FEM_PARPROJ_DIRICHLET_GHOST; fem_parproj_bc_phi_sol = GKYL_FEM_PARPROJ_DIRICHLET_SKIN; - f->fem_projection_par_rho_func = gk_field_fem_projection_par; - f->fem_projection_par_phi_func = gk_field_fem_projection_par_phi_iwl_3x; + gkf->fem_projection_par_rho_func = gk_field_fem_projection_par; + gkf->fem_projection_par_phi_func = gk_field_fem_projection_par_phi_iwl_3x; // Take the TS function from the parallel BC of the first species. int par_dir = app->cdim-1; // Parallel direction index. @@ -430,33 +495,33 @@ gk_field_2x3x_add_IWL_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field .shift_func_ctx = par_lower_bc->aux_ctx, .use_gpu = app->use_gpu, }; - f->bc_ts_lo = gkyl_bc_twistshift_new(&T_LU_lo); + gkf->bc_ts_lo = gkyl_bc_twistshift_new(&T_LU_lo); long buff_sz = GKYL_MAX2(app->global_lower_ghost_par_sol.volume, app->global_lower_ghost_par_core.volume); - f->bc_buffer = mkarr(app->use_gpu, app->basis.num_basis, buff_sz); + gkf->bc_buffer = mkarr(app->use_gpu, app->basis.num_basis, buff_sz); - f->gfss_bc_op_core_up = gkyl_bc_basic_gyrokinetic_new(par_dir, GKYL_UPPER_EDGE, GKYL_BC_GK_FIELD_BOUNDARY_VALUE, + gkf->gfss_bc_op_core_up = gkyl_bc_basic_gyrokinetic_new(par_dir, GKYL_UPPER_EDGE, GKYL_BC_GK_FIELD_BOUNDARY_VALUE, app->basis_on_dev, &app->global_upper_skin_par_core, &app->global_upper_ghost_par_core, app->basis.num_basis, app->cdim, app->use_gpu); // Write the discrete shift to file. - gk_field_3x_write_twistshift(app, f); + gk_field_3x_write_twistshift(app, gkf); } // Parallel smoother for the charge density. - f->fem_parproj_rho_core = gkyl_fem_parproj_new(&app->global_core, &app->grid, &app->basis, + gkf->fem_parproj_rho_core = gkyl_fem_parproj_new(&app->global_core, &app->grid, &app->basis, fem_parproj_bc_rho_core, 0, 0, 0, app->use_gpu); - f->fem_parproj_rho_sol = gkyl_fem_parproj_new(&app->global_sol, &app->grid, &app->basis, + gkf->fem_parproj_rho_sol = gkyl_fem_parproj_new(&app->global_sol, &app->grid, &app->basis, fem_parproj_bc_rho_sol, 0, 0, 0, app->use_gpu); // Fill bias line list for fem_parproj_phi. - gk_field_2x3x_fill_fem_parproj_bias_lines(app, f, poisson_bcs); + gk_field_2x3x_fill_fem_parproj_bias_lines(app, gkf, poisson_bcs); // Parallel smoother for the potential. - f->fem_parproj_phi_core = gkyl_fem_parproj_new(&app->global_core, &app->grid, &app->basis, - fem_parproj_bc_phi_core, &f->fem_parproj_bias_line_list, 0, 0, app->use_gpu); - f->fem_parproj_phi_sol = gkyl_fem_parproj_new(&app->global_sol, &app->grid, &app->basis, - fem_parproj_bc_phi_sol, &f->fem_parproj_bias_line_list, 0, 0, app->use_gpu); + gkf->fem_parproj_phi_core = gkyl_fem_parproj_new(&app->global_core, &app->grid, &app->basis, + fem_parproj_bc_phi_core, &gkf->fem_parproj_bias_line_list, 0, 0, app->use_gpu); + gkf->fem_parproj_phi_sol = gkyl_fem_parproj_new(&app->global_sol, &app->grid, &app->basis, + fem_parproj_bc_phi_sol, &gkf->fem_parproj_bias_line_list, 0, 0, app->use_gpu); } @@ -513,6 +578,8 @@ gk_field_fem_release_2x3x(const gkyl_gyrokinetic_app *app, struct gk_field *f) gkyl_fem_parproj_release(f->fem_parproj_phi_core); if (app->cdim == 3) { + gkyl_dg_interpolate_release(f->bc_ts_prolong); + gkyl_dg_interpolate_release(f->bc_ts_coarsen); gkyl_bc_twistshift_release(f->bc_ts_lo); gkyl_bc_basic_gyrokinetic_release(f->gfss_bc_op_core_up); gkyl_array_release(f->bc_buffer); diff --git a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h index 15fce8716c..09380dc4cb 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h @@ -1320,6 +1320,13 @@ struct gk_field { void (*calc_energy_dt_func)(gkyl_gyrokinetic_app *app, const struct gk_field *field, double dt, double *energy_reduced); // Objects used in IWL simulations and TS BCs. + struct gkyl_rect_grid bc_ts_grid; // Higher resolution grid for TS BC. + struct gkyl_range bc_ts_global_ext, bc_ts_global; // Higher resolution ranges for TS BC. + struct gkyl_range bc_ts_global_lower_skin_par, bc_ts_global_upper_skin_par; // Parallel skin for TS BC. + struct gkyl_range bc_ts_global_lower_ghost_par, bc_ts_global_upper_ghost_par; // Parallel ghost for TS BC. + struct gkyl_dg_interpolate *bc_ts_prolong, *bc_ts_coarsen; // Interpolation operators for TS BC. + struct gkyl_array *bc_ts_buffer_fine, *bc_ts_buffer_coar; // Buffer for TS BCs. + struct gkyl_range bc_ts_global_par_ext; // Range extended in parallel direction for TS BC. struct gkyl_bc_twistshift *bc_ts_lo; // Fills lower core z-ghost with TS BC. struct gkyl_bc_basic_gyrokinetic *gfss_bc_op_core_up; // Fills upper core z-ghost with skin boundary value. struct gkyl_array *bc_buffer; // Buffer for bc_basic. From d9d0d8444389dd9c1085255a85b541e9f38ad1ba Mon Sep 17 00:00:00 2001 From: manauref Date: Mon, 11 May 2026 10:44:01 -0600 Subject: [PATCH 02/30] Apply TS BC to the distributions on a higher resolution grid too. Runs on CPU, not yet tested on GPU. --- gyrokinetic/apps/gk_species.c | 142 ++++++++++++++++++++--- gyrokinetic/apps/gkyl_gyrokinetic_priv.h | 8 ++ 2 files changed, 135 insertions(+), 15 deletions(-) diff --git a/gyrokinetic/apps/gk_species.c b/gyrokinetic/apps/gk_species.c index 3d0159345d..f6c246d60a 100644 --- a/gyrokinetic/apps/gk_species.c +++ b/gyrokinetic/apps/gk_species.c @@ -188,6 +188,56 @@ gk_species_rhs_implicit_static(gkyl_gyrokinetic_app *app, struct gk_species *spe return app->cfl/omega_cfl; } +static void +gk_species_apply_ts_bc_interp(gkyl_gyrokinetic_app *app, const struct gk_species *gks, + enum gkyl_edge_loc edge, struct gkyl_array *fin, struct gkyl_array *fout) +{ + // Apply twistshift BC to the quantity `fin`, on a higher resolution grid, and place the output in `fout` + + int par_dir = app->cdim-1; // Parallel direction index. + // Copy ghost into skin so it gets interpolated. + if (edge == GKYL_LOWER_EDGE) + gkyl_array_copy_range_to_range(gks->bc_ts_buffer_coar, fin, + &gks->local_lower_skin[par_dir], &gks->local_lower_ghost[par_dir]); + else if (edge == GKYL_UPPER_EDGE) + gkyl_array_copy_range_to_range(gks->bc_ts_buffer_coar, fin, + &gks->local_upper_skin[par_dir], &gks->local_upper_ghost[par_dir]); + + // Prolong (interpolate up to higher resolution). + gkyl_dg_interpolate_advance(gks->bc_ts_prolong, gks->bc_ts_buffer_coar, gks->bc_ts_buffer_fine); + + // Copy skin to ghost and apply TSBC. + if (edge == GKYL_LOWER_EDGE) { + gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, + &gks->bc_ts_local_lower_ghost_par, &gks->bc_ts_local_upper_skin_par); + gkyl_bc_twistshift_advance(gks->bc_ts_lo, gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine); + } + else if (edge == GKYL_UPPER_EDGE) { + gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, + &gks->bc_ts_local_upper_ghost_par, &gks->bc_ts_local_lower_skin_par); + gkyl_bc_twistshift_advance(gks->bc_ts_up, gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine); + } + + // Copy TS-ed ghost into skin so it gets coarsened. + if (edge == GKYL_LOWER_EDGE) + gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, + &gks->bc_ts_local_lower_skin_par, &gks->bc_ts_local_lower_ghost_par); + else if (edge == GKYL_UPPER_EDGE) + gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, + &gks->bc_ts_local_upper_skin_par, &gks->bc_ts_local_upper_ghost_par); + + // Restrict (coarsen to lower resolution). + gkyl_dg_interpolate_advance(gks->bc_ts_coarsen, gks->bc_ts_buffer_fine, gks->bc_ts_buffer_coar); + + // Copy skin back into ghost. + if (edge == GKYL_LOWER_EDGE) + gkyl_array_copy_range_to_range(fout, gks->bc_ts_buffer_coar, + &gks->local_lower_ghost[par_dir], &gks->local_lower_skin[par_dir]); + else if (edge == GKYL_UPPER_EDGE) + gkyl_array_copy_range_to_range(fout, gks->bc_ts_buffer_coar, + &gks->local_upper_ghost[par_dir], &gks->local_upper_skin[par_dir]); +} + static void gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species *species, struct gkyl_array *f) { @@ -206,7 +256,8 @@ gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species * app->field->phi_wall_lo, f, &app->local); break; case GKYL_BC_GK_SPECIES_TWISTSHIFT: - gkyl_bc_twistshift_advance(species->bc_ts_lo, f, f); +// gkyl_bc_twistshift_advance(species->bc_ts_lo, f, f); + gk_species_apply_ts_bc_interp(app, species, GKYL_LOWER_EDGE, f, f); break; case GKYL_BC_GK_SPECIES_IWL: gkyl_bc_sheath_gyrokinetic_advance(species->bc_sheath_lo, app->field->phi_smooth, @@ -235,7 +286,8 @@ gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species * app->field->phi_wall_up, f, &app->local); break; case GKYL_BC_GK_SPECIES_TWISTSHIFT: - gkyl_bc_twistshift_advance(species->bc_ts_up, f, f); +// gkyl_bc_twistshift_advance(species->bc_ts_up, f, f); + gk_species_apply_ts_bc_interp(app, species, GKYL_UPPER_EDGE, f, f); break; case GKYL_BC_GK_SPECIES_IWL: gkyl_bc_sheath_gyrokinetic_advance(species->bc_sheath_up, app->field->phi_smooth, @@ -733,14 +785,12 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app int cdim = app->cdim, vdim = gks->info.vdim; int pdim = cdim+vdim; - int ghost[GKYL_MAX_DIM]; - + int num_ghost[GKYL_MAX_DIM]; for (int d=0; dbc_buffer_up_fixed = need_bc_buffer_up_fixed? mkarr(app->use_gpu, gks->basis.num_basis, buff_sz) : mkarr(app->use_gpu, 1, 1); + int par_dir = app->cdim-1; // Parallel direction index. + if (gks->lower_bc[par_dir].type == GKYL_BC_GK_SPECIES_TWISTSHIFT && + gks->upper_bc[par_dir].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { + // Initialize an interpolation operator on a higher resolution grid, with + // twice as many cells along x and y. + int ts_cells[GKYL_MAX_DIM]; + for (int d=0; dgrid.cells[d]; + } + for (int d=-1; dgrid.cells[cdim+d]; + } + gkyl_rect_grid_init(&gks->bc_ts_grid, pdim, gks->grid.lower, gks->grid.upper, ts_cells); + + struct gkyl_range bc_ts_global_ext, bc_ts_global; + gkyl_create_grid_ranges(&gks->bc_ts_grid, num_ghost, &bc_ts_global_ext, &bc_ts_global); + + // Create a communicator. + int bc_ts_cuts[GKYL_MAX_DIM] = {0}; + gkyl_rect_decomp_get_cuts(app->decomp, bc_ts_cuts); + // Set velocity space cuts to 1 as we do not use MPI in vel-space. + for (int d=0; dcomm, 0, bc_ts_decomp); + + // Local range. + int my_rank = 0; + gkyl_comm_get_rank(bc_ts_comm, &my_rank); + + gkyl_create_ranges(&bc_ts_decomp->ranges[my_rank], num_ghost, &gks->bc_ts_local_ext, &gks->bc_ts_local); + + // Skin and ghost ranges for configuration space fields. + gkyl_skin_ghost_ranges(&gks->bc_ts_local_lower_skin_par, &gks->bc_ts_local_lower_ghost_par, + par_dir, GKYL_LOWER_EDGE, &gks->bc_ts_local_ext, num_ghost); + gkyl_skin_ghost_ranges(&gks->bc_ts_local_upper_skin_par, &gks->bc_ts_local_upper_ghost_par, + par_dir, GKYL_UPPER_EDGE, &gks->bc_ts_local_ext, num_ghost); + + gks->bc_ts_prolong = gkyl_dg_interpolate_new(app->cdim, &gks->basis, + &gks->grid, &gks->bc_ts_grid, &gks->local, &gks->bc_ts_local, num_ghost, app->use_gpu); + gks->bc_ts_coarsen = gkyl_dg_interpolate_new(app->cdim, &gks->basis, + &gks->bc_ts_grid, &gks->grid, &gks->bc_ts_local, &gks->local, num_ghost, app->use_gpu); + + gks->bc_ts_buffer_fine = mkarr(app->use_gpu, gks->basis.num_basis, gks->bc_ts_local_ext.volume); + gks->bc_ts_buffer_coar = mkarr(app->use_gpu, gks->basis.num_basis, gks->local_ext.volume); + + // Create a local range extended in the BC dir. + int lower_bcdir_ext[pdim], upper_bcdir_ext[pdim]; + for (int i=0; ibc_ts_local.lower[i]; + upper_bcdir_ext[i] = gks->bc_ts_local.upper[i]; + } + lower_bcdir_ext[par_dir] = gks->bc_ts_local_ext.lower[par_dir]; + upper_bcdir_ext[par_dir] = gks->bc_ts_local_ext.upper[par_dir]; + gkyl_sub_range_init(&gks->bc_ts_local_par_ext, &gks->bc_ts_local_ext, lower_bcdir_ext, upper_bcdir_ext); + + gkyl_rect_decomp_release(bc_ts_decomp); + gkyl_comm_release(bc_ts_comm); + } + for (int d=0; dlower_bc[d].type == GKYL_BC_GK_SPECIES_SHEATH) { @@ -857,10 +969,10 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app .shear_dir = 0, // shift varies with x. .edge = GKYL_LOWER_EDGE, .cdim = cdim, - .bcdir_ext_update_r = gks->local_par_ext, - .num_ghost = ghost, + .bcdir_ext_update_r = gks->bc_ts_local_par_ext, + .num_ghost = num_ghost, .basis = gks->basis, - .grid = gks->grid, + .grid = gks->bc_ts_grid, .shift_func = gks->lower_bc[d].aux_profile, .shift_func_ctx = gks->lower_bc[d].aux_ctx, .use_gpu = app->use_gpu, @@ -883,7 +995,7 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app .edge = GKYL_LOWER_EDGE, .cdim = cdim, .bcdir_ext_update_r = gks->local_par_ext_core, - .num_ghost = ghost, + .num_ghost = num_ghost, .basis = gks->basis, .grid = gks->grid, .shift_func = gks->lower_bc[d].aux_profile, @@ -937,10 +1049,10 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app .shear_dir = 0, // shift varies with x. .edge = GKYL_UPPER_EDGE, .cdim = cdim, - .bcdir_ext_update_r = gks->local_par_ext, - .num_ghost = ghost, + .bcdir_ext_update_r = gks->bc_ts_local_par_ext, + .num_ghost = num_ghost, .basis = gks->basis, - .grid = gks->grid, + .grid = gks->bc_ts_grid, .shift_func = gks->upper_bc[d].aux_profile, .shift_func_ctx = gks->upper_bc[d].aux_ctx, .use_gpu = app->use_gpu, @@ -962,7 +1074,7 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app .edge = GKYL_UPPER_EDGE, .cdim = cdim, .bcdir_ext_update_r = gks->local_par_ext_core, - .num_ghost = ghost, + .num_ghost = num_ghost, .basis = gks->basis, .grid = gks->grid, .shift_func = gks->upper_bc[d].aux_profile, diff --git a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h index 09380dc4cb..49cd64a5bf 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h @@ -1030,6 +1030,14 @@ struct gk_species { struct gkyl_range local_upper_skin_par_sol , local_upper_ghost_par_sol; // GK IWL sims need a core range extended in z, and a TS BC updater. struct gkyl_range local_par_ext_core; // Core range extended in parallel direction. + // Objects used in IWL simulations and TS BCs. + struct gkyl_rect_grid bc_ts_grid; // Higher resolution grid for TS BC. + struct gkyl_range bc_ts_local_ext, bc_ts_local; // Higher resolution ranges for TS BC. + struct gkyl_range bc_ts_local_lower_skin_par, bc_ts_local_upper_skin_par; // Parallel skin for TS BC. + struct gkyl_range bc_ts_local_lower_ghost_par, bc_ts_local_upper_ghost_par; // Parallel ghost for TS BC. + struct gkyl_dg_interpolate *bc_ts_prolong, *bc_ts_coarsen; // Interpolation operators for TS BC. + struct gkyl_array *bc_ts_buffer_fine, *bc_ts_buffer_coar; // Buffer for TS BCs. + struct gkyl_range bc_ts_local_par_ext; // Range extended in parallel direction for TS BC. struct gkyl_bc_twistshift *bc_ts_lo, *bc_ts_up; struct gk_proj proj_init; // Projector for initial conditions. From 8ace1978b52ea25da7b009fe8082a620e9c13af0 Mon Sep 17 00:00:00 2001 From: manauref Date: Mon, 11 May 2026 18:17:04 -0700 Subject: [PATCH 03/30] Release TS interp objects in gk_species. --- gyrokinetic/apps/gk_species.c | 114 ++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/gyrokinetic/apps/gk_species.c b/gyrokinetic/apps/gk_species.c index f6c246d60a..7646598559 100644 --- a/gyrokinetic/apps/gk_species.c +++ b/gyrokinetic/apps/gk_species.c @@ -676,100 +676,110 @@ gk_species_write_L2norm_static(gkyl_gyrokinetic_app* app, struct gk_species *gks } static void -gk_species_release_dynamic(const gkyl_gyrokinetic_app* app, const struct gk_species *s) +gk_species_release_dynamic(const gkyl_gyrokinetic_app* app, const struct gk_species *gks) { // Release various arrays and objects for a dynamic species. - gkyl_array_release(s->f1); - gkyl_array_release(s->fnew); - gkyl_array_release(s->bc_buffer); - gkyl_array_release(s->bc_buffer_lo_fixed); - gkyl_array_release(s->bc_buffer_up_fixed); + gkyl_array_release(gks->f1); + gkyl_array_release(gks->fnew); + gkyl_array_release(gks->bc_buffer); + gkyl_array_release(gks->bc_buffer_lo_fixed); + gkyl_array_release(gks->bc_buffer_up_fixed); - if (s->info.write_omega_cfl) { - gkyl_array_release(s->cflrate_ho); + if (gks->info.write_omega_cfl) { + gkyl_array_release(gks->cflrate_ho); + } + + int par_dir = app->cdim-1; // Parallel direction index. + if (gks->lower_bc[par_dir].type == GKYL_BC_GK_SPECIES_TWISTSHIFT && + gks->upper_bc[par_dir].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { + // Release objects used for TS BCs. + gkyl_array_release(gks->bc_ts_buffer_fine); + gkyl_array_release(gks->bc_ts_buffer_coar); + gkyl_dg_interpolate_release(gks->bc_ts_prolong); + gkyl_dg_interpolate_release(gks->bc_ts_coarsen); } // Copy BCs are allocated by default. Need to free. for (int d=0; dcdim; ++d) { - if (s->lower_bc[d].type == GKYL_BC_GK_SPECIES_SHEATH) { - gkyl_bc_sheath_gyrokinetic_release(s->bc_sheath_lo); + if (gks->lower_bc[d].type == GKYL_BC_GK_SPECIES_SHEATH) { + gkyl_bc_sheath_gyrokinetic_release(gks->bc_sheath_lo); } - else if (s->lower_bc[d].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { - gkyl_bc_twistshift_release(s->bc_ts_lo); + else if (gks->lower_bc[d].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { + gkyl_bc_twistshift_release(gks->bc_ts_lo); } - else if (s->lower_bc[d].type == GKYL_BC_GK_SPECIES_IWL) { - gkyl_bc_sheath_gyrokinetic_release(s->bc_sheath_lo); + else if (gks->lower_bc[d].type == GKYL_BC_GK_SPECIES_IWL) { + gkyl_bc_sheath_gyrokinetic_release(gks->bc_sheath_lo); if (app->cdim == 3) { - gkyl_bc_twistshift_release(s->bc_ts_lo); + gkyl_bc_twistshift_release(gks->bc_ts_lo); } } - else if ( (s->lower_bc[d].type == GKYL_BC_GK_SPECIES_COPY) || - (s->lower_bc[d].type == GKYL_BC_GK_SPECIES_ABSORB) || - (s->lower_bc[d].type == GKYL_BC_GK_SPECIES_REFLECT) || - (s->lower_bc[d].type == GKYL_BC_GK_SPECIES_FIXED_FUNC) ) { - gkyl_bc_basic_gyrokinetic_release(s->bc_lo[d]); + else if ( (gks->lower_bc[d].type == GKYL_BC_GK_SPECIES_COPY) || + (gks->lower_bc[d].type == GKYL_BC_GK_SPECIES_ABSORB) || + (gks->lower_bc[d].type == GKYL_BC_GK_SPECIES_REFLECT) || + (gks->lower_bc[d].type == GKYL_BC_GK_SPECIES_FIXED_FUNC) ) { + gkyl_bc_basic_gyrokinetic_release(gks->bc_lo[d]); } - if (s->upper_bc[d].type == GKYL_BC_GK_SPECIES_SHEATH) { - gkyl_bc_sheath_gyrokinetic_release(s->bc_sheath_up); + if (gks->upper_bc[d].type == GKYL_BC_GK_SPECIES_SHEATH) { + gkyl_bc_sheath_gyrokinetic_release(gks->bc_sheath_up); } - else if (s->upper_bc[d].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { - gkyl_bc_twistshift_release(s->bc_ts_up); + else if (gks->upper_bc[d].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { + gkyl_bc_twistshift_release(gks->bc_ts_up); } - else if (s->upper_bc[d].type == GKYL_BC_GK_SPECIES_IWL) { - gkyl_bc_sheath_gyrokinetic_release(s->bc_sheath_up); + else if (gks->upper_bc[d].type == GKYL_BC_GK_SPECIES_IWL) { + gkyl_bc_sheath_gyrokinetic_release(gks->bc_sheath_up); if (app->cdim == 3) { - gkyl_bc_twistshift_release(s->bc_ts_up); + gkyl_bc_twistshift_release(gks->bc_ts_up); } } - else if ( (s->upper_bc[d].type == GKYL_BC_GK_SPECIES_COPY) || - (s->upper_bc[d].type == GKYL_BC_GK_SPECIES_ABSORB) || - (s->upper_bc[d].type == GKYL_BC_GK_SPECIES_REFLECT) || - (s->upper_bc[d].type == GKYL_BC_GK_SPECIES_FIXED_FUNC) ) { - gkyl_bc_basic_gyrokinetic_release(s->bc_up[d]); + else if ( (gks->upper_bc[d].type == GKYL_BC_GK_SPECIES_COPY) || + (gks->upper_bc[d].type == GKYL_BC_GK_SPECIES_ABSORB) || + (gks->upper_bc[d].type == GKYL_BC_GK_SPECIES_REFLECT) || + (gks->upper_bc[d].type == GKYL_BC_GK_SPECIES_FIXED_FUNC) ) { + gkyl_bc_basic_gyrokinetic_release(gks->bc_up[d]); } } if (app->use_gpu) { - gkyl_cu_free(s->omega_cfl); - gkyl_cu_free(s->m0_max); + gkyl_cu_free(gks->omega_cfl); + gkyl_cu_free(gks->m0_max); } else { - gkyl_free(s->omega_cfl); - gkyl_free(s->m0_max); + gkyl_free(gks->omega_cfl); + gkyl_free(gks->m0_max); } // Release integrated moment memory. - gk_species_moment_release(app, &s->integ_moms); + gk_species_moment_release(app, &gks->integ_moms); // Release integrated diag memory. - gkyl_dynvec_release(s->integ_diag); + gkyl_dynvec_release(gks->integ_diag); if (app->use_gpu) { - gkyl_cu_free(s->red_integ_diag); - gkyl_cu_free(s->red_integ_diag_global); + gkyl_cu_free(gks->red_integ_diag); + gkyl_cu_free(gks->red_integ_diag_global); } else { - gkyl_free(s->red_integ_diag); - gkyl_free(s->red_integ_diag_global); + gkyl_free(gks->red_integ_diag); + gkyl_free(gks->red_integ_diag_global); } // Release L2 norm memory. - gkyl_array_integrate_release(s->integ_wfsq_op); - gkyl_dynvec_release(s->L2norm); + gkyl_array_integrate_release(gks->integ_wfsq_op); + gkyl_dynvec_release(gks->L2norm); if (app->use_gpu) { - gkyl_cu_free(s->L2norm_local); - gkyl_cu_free(s->L2norm_global); + gkyl_cu_free(gks->L2norm_local); + gkyl_cu_free(gks->L2norm_global); } else { - gkyl_free(s->L2norm_local); - gkyl_free(s->L2norm_global); + gkyl_free(gks->L2norm_local); + gkyl_free(gks->L2norm_global); } - if (s->info.time_rate_diagnostics) { + if (gks->info.time_rate_diagnostics) { // Free df/dt diagnostics memory. - gkyl_array_release(s->fdot_mom_old); - gkyl_array_release(s->fdot_mom_new); - gkyl_dynvec_release(s->fdot_integ_diag); + gkyl_array_release(gks->fdot_mom_old); + gkyl_array_release(gks->fdot_mom_new); + gkyl_dynvec_release(gks->fdot_integ_diag); } } From 359728b9b039286ac6b58f020c453063dc1df165 Mon Sep 17 00:00:00 2001 From: manauref Date: Mon, 11 May 2026 20:39:10 -0700 Subject: [PATCH 04/30] Correct range in step that copies between skin and ghost in high res grid. --- gyrokinetic/apps/gk_species.c | 8 ++++++-- gyrokinetic/creg/rt_gk_tcv_core_3x2v_p1.c | 5 +++-- gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/gyrokinetic/apps/gk_species.c b/gyrokinetic/apps/gk_species.c index 7646598559..db0f2dd57b 100644 --- a/gyrokinetic/apps/gk_species.c +++ b/gyrokinetic/apps/gk_species.c @@ -209,12 +209,12 @@ gk_species_apply_ts_bc_interp(gkyl_gyrokinetic_app *app, const struct gk_species // Copy skin to ghost and apply TSBC. if (edge == GKYL_LOWER_EDGE) { gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, - &gks->bc_ts_local_lower_ghost_par, &gks->bc_ts_local_upper_skin_par); + &gks->bc_ts_local_lower_ghost_par, &gks->bc_ts_local_lower_skin_par); gkyl_bc_twistshift_advance(gks->bc_ts_lo, gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine); } else if (edge == GKYL_UPPER_EDGE) { gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, - &gks->bc_ts_local_upper_ghost_par, &gks->bc_ts_local_lower_skin_par); + &gks->bc_ts_local_upper_ghost_par, &gks->bc_ts_local_upper_skin_par); gkyl_bc_twistshift_advance(gks->bc_ts_up, gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine); } @@ -979,9 +979,11 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app .shear_dir = 0, // shift varies with x. .edge = GKYL_LOWER_EDGE, .cdim = cdim, +// .bcdir_ext_update_r = gks->local_par_ext, .bcdir_ext_update_r = gks->bc_ts_local_par_ext, .num_ghost = num_ghost, .basis = gks->basis, +// .grid = gks->grid, .grid = gks->bc_ts_grid, .shift_func = gks->lower_bc[d].aux_profile, .shift_func_ctx = gks->lower_bc[d].aux_ctx, @@ -1059,9 +1061,11 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app .shear_dir = 0, // shift varies with x. .edge = GKYL_UPPER_EDGE, .cdim = cdim, +// .bcdir_ext_update_r = gks->local_par_ext, .bcdir_ext_update_r = gks->bc_ts_local_par_ext, .num_ghost = num_ghost, .basis = gks->basis, +// .grid = gks->grid, .grid = gks->bc_ts_grid, .shift_func = gks->upper_bc[d].aux_profile, .shift_func_ctx = gks->upper_bc[d].aux_ctx, diff --git a/gyrokinetic/creg/rt_gk_tcv_core_3x2v_p1.c b/gyrokinetic/creg/rt_gk_tcv_core_3x2v_p1.c index d1d7e46c13..9eaf0e2a60 100644 --- a/gyrokinetic/creg/rt_gk_tcv_core_3x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_tcv_core_3x2v_p1.c @@ -741,8 +741,6 @@ main(int argc, char **argv) // GK app. struct gkyl_gk app_inp = { - .name = "gk_tcv_core_3x2v_p1", - .cfl_frac = 1.0, .cdim = ctx.cdim, @@ -775,6 +773,9 @@ main(int argc, char **argv) }, }; + // Set app output name from the executable name (argv[0]). + snprintf(app_inp.name, sizeof(app_inp.name), "%s", app_args.app_name); + struct gkyl_gyrokinetic_run_inp run_inp = { .app_inp = app_inp, .time_stepping = { diff --git a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c index c0db314231..a40c305418 100644 --- a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c @@ -865,6 +865,7 @@ main(int argc, char **argv) // Set app output name from the executable name (argv[0]). snprintf(app_inp.name, sizeof(app_inp.name), "%s", app_args.app_name); + struct gkyl_gyrokinetic_run_inp run_inp = { .app_inp = app_inp, .time_stepping = { From fffdf8b4560b0375824e7f750c59c2f0897ccb54 Mon Sep 17 00:00:00 2001 From: Manaure Francisquez Date: Tue, 16 Jun 2026 14:47:48 -0700 Subject: [PATCH 05/30] Restore machine files to match main. --- machines/configure.perlmutter.gpu.sh | 10 +++++----- machines/mkdeps.perlmutter.gpu.sh | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/machines/configure.perlmutter.gpu.sh b/machines/configure.perlmutter.gpu.sh index 7934bceb35..6cd1a66d2b 100755 --- a/machines/configure.perlmutter.gpu.sh +++ b/machines/configure.perlmutter.gpu.sh @@ -1,10 +1,10 @@ module load PrgEnv-gnu/8.6.0 module load craype-accel-nvidia80 -module load cray-mpich/8.1.30 -module load cudatoolkit/12.9 -module load nccl/2.18.3-cu12 +module load cray-mpich/9.0.1 +module load cudatoolkit/13.0 +module load nccl/2.29.2-cu13 module load cray-libsci/25.09.0 -: "${PREFIX:=/pscratch/sd/m/mana/gkeyll/code/gkeyll_gpu1/gkylsoft}" +: "${PREFIX:=$HOME/gkylsoft}" -./configure CC=nvcc ARCH_FLAGS="-march=native" CUDA_ARCH=80 --prefix=$PREFIX --lapack-lib-name=sci_gnu --lapack-inc=$CRAY_LIBSCI_PREFIX/include --lapack-lib=$CRAY_LIBSCI_PREFIX/lib --cudamath-lib=/opt/nvidia/hpc_sdk/Linux_x86_64/25.5/math_libs/12.9/lib64 --use-mpi=yes --mpi-inc=$CRAY_MPICH_DIR/include --mpi-lib=$CRAY_MPICH_DIR/lib --use-nccl=yes --nccl-inc=$NCCL_DIR/include --nccl-lib=$NCCL_DIR/lib --use-lua=yes --use-cudss=yes; +./configure CC=nvcc ARCH_FLAGS="-march=native" CUDA_ARCH=80 --prefix=$PREFIX --lapack-lib-name=sci_gnu --lapack-inc=$CRAY_LIBSCI_PREFIX/include --lapack-lib=$CRAY_LIBSCI_PREFIX/lib --cudamath-lib=/opt/nvidia/hpc_sdk/Linux_x86_64/25.9/math_libs/13.0/lib64 --use-mpi=yes --mpi-inc=$CRAY_MPICH_DIR/include --mpi-lib=$CRAY_MPICH_DIR/lib --use-nccl=yes --nccl-inc=$NCCL_DIR/include --nccl-lib=$NCCL_DIR/lib --use-lua=yes --use-cudss=yes; diff --git a/machines/mkdeps.perlmutter.gpu.sh b/machines/mkdeps.perlmutter.gpu.sh index 3008aad8da..3989e63124 100755 --- a/machines/mkdeps.perlmutter.gpu.sh +++ b/machines/mkdeps.perlmutter.gpu.sh @@ -1,11 +1,11 @@ module load PrgEnv-gnu/8.6.0 module load craype-accel-nvidia80 -module load cray-mpich/8.1.30 -module load cudatoolkit/12.9 -module load nccl/2.18.3-cu12 +module load cray-mpich/9.0.1 +module load cudatoolkit/13.0 +module load nccl/2.29.2-cu13 module load cray-libsci/25.09.0 -: "${PREFIX:=/pscratch/sd/m/mana/gkeyll/code/gkeyll_gpu1/gkylsoft}" +: "${PREFIX:=$HOME/gkylsoft}" cd install-deps ./mkdeps.sh --build-superlu=yes --build-cudss=yes --prefix=$PREFIX --build-luajit=yes MPICC=mpicc MPICXX=mpicxx From 387aac4f5fdb3282850a3daa1cdb8f382317700c Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Fri, 24 Jul 2026 10:49:42 -0400 Subject: [PATCH 06/30] Add a low pass filter updater. We can now apply upsampling, filtering, and down sampling in the twist and shift updater. --- core/unit/ctest_dg_lowpass_filter.c | 298 +++++++++++++++++++++ core/zero/dg_lowpass_filter.c | 94 +++++++ core/zero/gkyl_dg_lowpass_filter.h | 49 ++++ core/zero/gkyl_dg_lowpass_filter_priv.h | 36 +++ gyrokinetic/apps/gk_field_2x3x.c | 72 +---- gyrokinetic/apps/gk_species.c | 128 ++------- gyrokinetic/apps/gkyl_gyrokinetic.h | 5 + gyrokinetic/apps/gkyl_gyrokinetic_priv.h | 3 + gyrokinetic/apps/gyrokinetic.c | 3 + gyrokinetic/zero/bc_twistshift.c | 147 ++++++++-- gyrokinetic/zero/gkyl_bc_twistshift.h | 3 + gyrokinetic/zero/gkyl_bc_twistshift_priv.h | 14 + 12 files changed, 653 insertions(+), 199 deletions(-) create mode 100644 core/unit/ctest_dg_lowpass_filter.c create mode 100644 core/zero/dg_lowpass_filter.c create mode 100644 core/zero/gkyl_dg_lowpass_filter.h create mode 100644 core/zero/gkyl_dg_lowpass_filter_priv.h diff --git a/core/unit/ctest_dg_lowpass_filter.c b/core/unit/ctest_dg_lowpass_filter.c new file mode 100644 index 0000000000..a67a3cc72d --- /dev/null +++ b/core/unit/ctest_dg_lowpass_filter.c @@ -0,0 +1,298 @@ +// Test the dg_lowpass_filter updater: a low-pass Blackman-windowed sinc FIR +// filter applied along one direction of a DG field, meant e.g. to de-alias +// the twist-shift BC by removing content beyond the coarse-grid resolution +// before restriction. +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static struct gkyl_array* +mkarr(long nc, long size) +{ + struct gkyl_array* a = gkyl_array_new(GKYL_DOUBLE, nc, size); + return a; +} + +static double +filter_gain(int M, double fc, double freq) +{ + // Frequency response of the normalized Blackman-windowed sinc kernel + // at freq cycles/cell (real, since the kernel is symmetric). + double wsum = 0.0, gain = 0.0; + for (int k=-M; kmode_num*xn[0]); +} + +void eval_gauss_1x(double t, const double *xn, double *fout, void *ctx) +{ + fout[0] = exp(-pow((xn[0]-0.5)/0.04, 2)); +} + +void eval_mode_3x(double t, const double *xn, double *fout, void *ctx) +{ + // Separable function: an x-Nyquist mode (for a 32 cell grid on [0,1]) + // times a smooth (y,z) profile. + struct mode_ctx *mctx = ctx; + fout[0] = cos(2.0*M_PI*mctx->mode_num*xn[0]) + * (1.0 + 0.3*cos(2.0*M_PI*xn[1])) * (1.0 + 0.2*xn[2]); +} + +static void +test_1x(void) +{ + int poly_order = 1; + int cells[] = {32}; + double lower[] = {0.0}, upper[] = {1.0}; + int nghost[] = {1}; + int M = 8; + double fc = 0.3; + + struct gkyl_rect_grid grid; + gkyl_rect_grid_init(&grid, 1, lower, upper, cells); + struct gkyl_basis basis; + gkyl_cart_modal_serendip(&basis, 1, poly_order); + + struct gkyl_range local, local_ext; + gkyl_create_grid_ranges(&grid, nghost, &local_ext, &local); + + struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, M, + grid.dx[0]/fc, &basis, &grid, &local, false); + + struct gkyl_array *fin = mkarr(basis.num_basis, local_ext.volume); + struct gkyl_array *fout = mkarr(basis.num_basis, local_ext.volume); + + // a) A constant field is preserved exactly everywhere, including at + // the boundaries where the stencil is truncated and renormalized. + gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, + poly_order+1, 1, eval_const_1x, NULL); + gkyl_proj_on_basis_advance(proj, 0.0, &local, fin); + gkyl_proj_on_basis_release(proj); + + gkyl_array_clear(fout, 7.0); + gkyl_dg_lowpass_filter_advance(lpf, fin, fout); + + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &local); + while (gkyl_range_iter_next(&iter)) { + long linidx = gkyl_range_idx(&local, iter.idx); + const double *in_c = gkyl_array_cfetch(fin, linidx); + const double *out_c = gkyl_array_cfetch(fout, linidx); + for (int c=0; c local.upper[0]-M) + continue; + long linidx = gkyl_range_idx(&local, iter.idx); + const double *in_c = gkyl_array_cfetch(fin, linidx); + const double *out_c = gkyl_array_cfetch(fout, linidx); + for (int c=0; c local.upper[0]-M) + continue; + long linidx = gkyl_range_idx(&local, iter.idx); + const double *in_c = gkyl_array_cfetch(fin, linidx); + const double *out_c = gkyl_array_cfetch(fout, linidx); + for (int c=0; c local.upper[0]-M) + continue; + const double *out_c = gkyl_array_cfetch(fout, gkyl_range_idx(&local, iter.idx)); + for (int c=0; c local.upper[0]-M) + continue; + long linidx = gkyl_range_idx(&local, iter.idx); + const double *in_c = gkyl_array_cfetch(fin, linidx); + const double *out_c = gkyl_array_cfetch(fout, linidx); + for (int c=0; c +#include +#include + +struct gkyl_dg_lowpass_filter* +gkyl_dg_lowpass_filter_new(int dir, int half_width, double cutoff_wavelength, + const struct gkyl_basis *basis, const struct gkyl_rect_grid *grid, + const struct gkyl_range *range, bool use_gpu) +{ + // Allocate space for new updater. + struct gkyl_dg_lowpass_filter *up = gkyl_malloc(sizeof(*up)); + + up->use_gpu = use_gpu; + assert(!up->use_gpu); // GPU implementation pending. + up->ndim = basis->ndim; + up->dir = dir; + up->half_width = half_width; + up->num_basis = basis->num_basis; + up->grid = *grid; + up->range = *range; + + // Perform some basic checks: + assert(grid->ndim == range->ndim); + assert(0 <= dir && dir < grid->ndim); + assert(half_width > 0); + + // Normalized cutoff frequency in cycles per cell. + double fc = grid->dx[dir]/cutoff_wavelength; + assert(0.0 < fc && fc <= 0.5); + + up->weights = gkyl_malloc((2*half_width+1)*sizeof(double)); + dg_lpf_calc_weights(half_width, fc, up->weights); + + return up; +} + +void +gkyl_dg_lowpass_filter_advance(gkyl_dg_lowpass_filter *up, + struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) +{ + assert(fdo != ftar); // Stencil operation, can't be done in-place. + assert(fdo->ncomp == ftar->ncomp); + assert(fdo->size == ftar->size); + assert(fdo->ncomp == up->num_basis); + + int dir = up->dir; + int M = up->half_width; + int num_basis = up->num_basis; + int idx_do[GKYL_MAX_DIM]; + + // Loop over the target range. + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &up->range); + while (gkyl_range_iter_next(&iter)) { + + long linidx_tar = gkyl_range_idx(&up->range, iter.idx); + double *ftar_c = gkyl_array_fetch(ftar, linidx_tar); + + // Truncate the stencil at the boundaries of the range. + int koff_lo = GKYL_MAX2(-M, up->range.lower[dir]-iter.idx[dir]); + int koff_up = GKYL_MIN2( M, up->range.upper[dir]-iter.idx[dir]); + + gkyl_copy_int_arr(up->range.ndim, iter.idx, idx_do); + + for (int c=0; crange, idx_do); + const double *fdo_c = gkyl_array_cfetch(fdo, linidx_do); + + double w = up->weights[k+M]; + for (int c=0; cweights); + gkyl_free(up); +} diff --git a/core/zero/gkyl_dg_lowpass_filter.h b/core/zero/gkyl_dg_lowpass_filter.h new file mode 100644 index 0000000000..8782b2447f --- /dev/null +++ b/core/zero/gkyl_dg_lowpass_filter.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include + +// Object type. +typedef struct gkyl_dg_lowpass_filter gkyl_dg_lowpass_filter; + +/** + * Create a new updater that low-pass filters a DG field along one direction + * with a Blackman-windowed sinc kernel, normalized to preserve the zero mode. + * + * @param dir Direction along which to filter. + * @param half_width Stencil half-width M in cells (stencil spans 2M+1 cells). + * @param cutoff_wavelength Cutoff wavelength (physical units). Normalized + * cutoff f_c = dx/cutoff_wavelength must satisfy 0 < f_c <= 0.5. + * @param basis DG basis of the filtered field. + * @param grid Grid the filtered field is defined on. + * @param range Range to filter in. The stencil is truncated and renormalized + * at its boundaries along dir. + * @param use_gpu bool to determine if on GPU. + * @return New filter updater. + */ +struct gkyl_dg_lowpass_filter* +gkyl_dg_lowpass_filter_new(int dir, int half_width, double cutoff_wavelength, + const struct gkyl_basis *basis, const struct gkyl_rect_grid *grid, + const struct gkyl_range *range, bool use_gpu); + +/** + * Run the filter updater. Cannot be used in-place; ftar cells outside the + * range are left untouched. + * + * @param up Filter updater. + * @param fdo Donor field. + * @param ftar Target (filtered) field. + */ +void +gkyl_dg_lowpass_filter_advance(gkyl_dg_lowpass_filter *up, + struct gkyl_array *fdo, struct gkyl_array *ftar); + +/** + * Release the memory associated with this filter updater. + * + * @param up Filter updater. + */ +void +gkyl_dg_lowpass_filter_release(gkyl_dg_lowpass_filter *up); diff --git a/core/zero/gkyl_dg_lowpass_filter_priv.h b/core/zero/gkyl_dg_lowpass_filter_priv.h new file mode 100644 index 0000000000..e301d86329 --- /dev/null +++ b/core/zero/gkyl_dg_lowpass_filter_priv.h @@ -0,0 +1,36 @@ +#pragma once + +// Private header for dg_lowpass_filter updater, not for direct use in user code. + +#include +#include +#include +#include + +// Primary struct in this updater. +struct gkyl_dg_lowpass_filter { + int ndim; // Dimensionality of the field. + bool use_gpu; // Whether to use the GPU. + int dir; // Direction along which to filter. + int half_width; // Stencil half-width M (stencil spans 2M+1 cells). + int num_basis; // Number of DG coefficients per cell. + struct gkyl_rect_grid grid; // Grid the field is defined on. + struct gkyl_range range; // Range to filter in. + double *weights; // 2M+1 filter weights, normalized to sum to 1. +}; + +static void +dg_lpf_calc_weights(int half_width, double fc, double *weights) +{ + // Sinc (cutoff fc in cycles/cell) times a Blackman window, normalized. + int M = half_width; + double wsum = 0.0; + for (int k=-M; kbc_ts_prolong, fin, gkf->bc_ts_buffer_fine); - - // Coppy upper skin to lower ghost and apply TSBC - gkyl_array_copy_range_to_range(gkf->bc_ts_buffer_fine, gkf->bc_ts_buffer_fine, - &gkf->bc_ts_global_lower_ghost_par, &gkf->bc_ts_global_upper_skin_par); - gkyl_bc_twistshift_advance(gkf->bc_ts_lo, gkf->bc_ts_buffer_fine, gkf->bc_ts_buffer_fine); - - // Copy TS-ed ghost into skin so it gets coarsened. - gkyl_array_copy_range_to_range(gkf->bc_ts_buffer_fine, gkf->bc_ts_buffer_fine, - &gkf->bc_ts_global_lower_skin_par, &gkf->bc_ts_global_lower_ghost_par); - // Restrict (coarsen to lower resolution). - gkyl_dg_interpolate_advance(gkf->bc_ts_coarsen, gkf->bc_ts_buffer_fine, gkf->bc_ts_buffer_coar); - - // Copy skin back into ghost. - int par_dir = app->cdim-1; // Parallel direction index. - gkyl_array_copy_range_to_range(fout, gkf->bc_ts_buffer_coar, - &app->global_lower_ghost[par_dir], &app->global_lower_skin[par_dir]); + // Apply the twist-shift BC in the lower parallel ghost. + gkyl_bc_twistshift_advance(gkf->bc_ts_lo, fin, fout); } static void @@ -278,44 +260,8 @@ gk_field_2x3x_add_TS_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field gkf->fem_projection_par_rho_func = gk_field_fem_projection_par; gkf->fem_projection_par_phi_func = gk_field_fem_projection_par_phi_ts_3x; - // Initialize an interpolation operator on a higher resolution grid, with - // twice as many cells along x and y. - int ts_cells[GKYL_MAX_CDIM]; - for (int d=0; dcdim-1; d++) { - ts_cells[d] = 2*app->grid.cells[d]; - } - ts_cells[app->cdim-1] = app->grid.cells[app->cdim-1]; - gkyl_rect_grid_init(&gkf->bc_ts_grid, app->cdim, app->grid.lower, app->grid.upper, ts_cells); - - int ghost_do[GKYL_MAX_CDIM]; - for (int d=0; dcdim; d++) ghost_do[d] = 1; - gkyl_create_grid_ranges(&gkf->bc_ts_grid, ghost_do, &gkf->bc_ts_global_ext, &gkf->bc_ts_global); - - // Skin and ghost ranges for configuration space fields. int num_ghost[] = {1, 1, 1}; int par_dir = app->cdim-1; // Parallel direction index. - gkyl_skin_ghost_ranges(&gkf->bc_ts_global_lower_skin_par, &gkf->bc_ts_global_lower_ghost_par, - par_dir, GKYL_LOWER_EDGE, &gkf->bc_ts_global_ext, num_ghost); - gkyl_skin_ghost_ranges(&gkf->bc_ts_global_upper_skin_par, &gkf->bc_ts_global_upper_ghost_par, - par_dir, GKYL_UPPER_EDGE, &gkf->bc_ts_global_ext, num_ghost); - - gkf->bc_ts_prolong = gkyl_dg_interpolate_new(app->cdim, &app->basis, - &app->grid, &gkf->bc_ts_grid, &app->global_ext, &gkf->bc_ts_global_ext, ghost_do, app->use_gpu); - gkf->bc_ts_coarsen = gkyl_dg_interpolate_new(app->cdim, &app->basis, - &gkf->bc_ts_grid, &app->grid, &gkf->bc_ts_global_ext, &app->global_ext, ghost_do, app->use_gpu); - - gkf->bc_ts_buffer_fine = mkarr(app->use_gpu, app->basis.num_basis, gkf->bc_ts_global_ext.volume); - gkf->bc_ts_buffer_coar = mkarr(app->use_gpu, app->basis.num_basis, app->global_ext.volume); - - // Create a global extended in the BC dir. - int lower_bcdir_ext[app->cdim], upper_bcdir_ext[app->cdim]; - for (int i=0; icdim; i++) { - lower_bcdir_ext[i] = gkf->bc_ts_global.lower[i]; - upper_bcdir_ext[i] = gkf->bc_ts_global.upper[i]; - } - lower_bcdir_ext[par_dir] = gkf->bc_ts_global_ext.lower[par_dir]; - upper_bcdir_ext[par_dir] = gkf->bc_ts_global_ext.upper[par_dir]; - gkyl_sub_range_init(&gkf->bc_ts_global_par_ext, &gkf->bc_ts_global_ext, lower_bcdir_ext, upper_bcdir_ext); // TS BC updater for lower edge. struct gkyl_bc_twistshift_inp T_LU_lo = { @@ -324,11 +270,14 @@ gk_field_2x3x_add_TS_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field .shear_dir = 0, // shift varies with x. .edge = GKYL_LOWER_EDGE, .cdim = app->cdim, - .bcdir_ext_update_r = gkf->bc_ts_global_par_ext, + .bcdir_ext_update_r = app->global_par_ext, .num_ghost = num_ghost, // one ghost per config direction .basis = app->basis, - .grid = gkf->bc_ts_grid, + .grid = app->grid, .use_gpu = app->use_gpu, + .upsample_factor = app->ts_upsample_factor, + .filter_half_width = app->ts_filter_half_width, + .filter_cutoff_wavelength = app->ts_filter_cutoff_wavelength, }; if (app->gk_geom->geometry_id == GKYL_GEOMETRY_TOKAMAK) T_LU_lo.shift_dg = app->delta_ts_x_lo; @@ -348,8 +297,11 @@ gk_field_2x3x_add_TS_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field .bcdir_ext_update_r = app->global_par_ext, .num_ghost = num_ghost, // one ghost per config direction .basis = app->basis, - .grid = gkf->bc_ts_grid, + .grid = app->grid, .use_gpu = app->use_gpu, + .upsample_factor = app->ts_upsample_factor, + .filter_half_width = app->ts_filter_half_width, + .filter_cutoff_wavelength = app->ts_filter_cutoff_wavelength, }; if (app->gk_geom->geometry_id == GKYL_GEOMETRY_TOKAMAK) T_UL_up.shift_dg = app->delta_ts_x_up; @@ -527,8 +479,6 @@ gk_field_fem_release_2x3x(const gkyl_gyrokinetic_app *app, struct gk_field *f) gkyl_fem_parproj_release(f->fem_parproj_phi_core); if (app->cdim == 3) { - gkyl_dg_interpolate_release(f->bc_ts_prolong); - gkyl_dg_interpolate_release(f->bc_ts_coarsen); gkyl_bc_twistshift_release(f->bc_ts_lo); gkyl_bc_twistshift_release(f->bc_ts_up); gkyl_bc_basic_gyrokinetic_release(f->gfss_bc_op_core_up); diff --git a/gyrokinetic/apps/gk_species.c b/gyrokinetic/apps/gk_species.c index d8bbcc1d9e..2fc13cf1ae 100644 --- a/gyrokinetic/apps/gk_species.c +++ b/gyrokinetic/apps/gk_species.c @@ -200,50 +200,8 @@ static void gk_species_apply_ts_bc_interp(gkyl_gyrokinetic_app *app, const struct gk_species *gks, enum gkyl_edge_loc edge, struct gkyl_array *fin, struct gkyl_array *fout) { - // Apply twistshift BC to the quantity `fin`, on a higher resolution grid, and place the output in `fout` - - int par_dir = app->cdim-1; // Parallel direction index. - // Copy ghost into skin so it gets interpolated. - if (edge == GKYL_LOWER_EDGE) - gkyl_array_copy_range_to_range(gks->bc_ts_buffer_coar, fin, - &gks->local_lower_skin[par_dir], &gks->local_lower_ghost[par_dir]); - else if (edge == GKYL_UPPER_EDGE) - gkyl_array_copy_range_to_range(gks->bc_ts_buffer_coar, fin, - &gks->local_upper_skin[par_dir], &gks->local_upper_ghost[par_dir]); - - // Prolong (interpolate up to higher resolution). - gkyl_dg_interpolate_advance(gks->bc_ts_prolong, gks->bc_ts_buffer_coar, gks->bc_ts_buffer_fine); - - // Copy skin to ghost and apply TSBC. - if (edge == GKYL_LOWER_EDGE) { - gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, - &gks->bc_ts_local_lower_ghost_par, &gks->bc_ts_local_lower_skin_par); - gkyl_bc_twistshift_advance(gks->bc_ts_lo, gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine); - } - else if (edge == GKYL_UPPER_EDGE) { - gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, - &gks->bc_ts_local_upper_ghost_par, &gks->bc_ts_local_upper_skin_par); - gkyl_bc_twistshift_advance(gks->bc_ts_up, gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine); - } - - // Copy TS-ed ghost into skin so it gets coarsened. - if (edge == GKYL_LOWER_EDGE) - gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, - &gks->bc_ts_local_lower_skin_par, &gks->bc_ts_local_lower_ghost_par); - else if (edge == GKYL_UPPER_EDGE) - gkyl_array_copy_range_to_range(gks->bc_ts_buffer_fine, gks->bc_ts_buffer_fine, - &gks->bc_ts_local_upper_skin_par, &gks->bc_ts_local_upper_ghost_par); - - // Restrict (coarsen to lower resolution). - gkyl_dg_interpolate_advance(gks->bc_ts_coarsen, gks->bc_ts_buffer_fine, gks->bc_ts_buffer_coar); - - // Copy skin back into ghost. - if (edge == GKYL_LOWER_EDGE) - gkyl_array_copy_range_to_range(fout, gks->bc_ts_buffer_coar, - &gks->local_lower_ghost[par_dir], &gks->local_lower_skin[par_dir]); - else if (edge == GKYL_UPPER_EDGE) - gkyl_array_copy_range_to_range(fout, gks->bc_ts_buffer_coar, - &gks->local_upper_ghost[par_dir], &gks->local_upper_skin[par_dir]); + // Apply the twist-shift BC at the given edge. + gkyl_bc_twistshift_advance(edge == GKYL_LOWER_EDGE? gks->bc_ts_lo : gks->bc_ts_up, fin, fout); } static void @@ -726,14 +684,6 @@ gk_species_release_dynamic(const gkyl_gyrokinetic_app* app, const struct gk_spec } int par_dir = app->cdim-1; // Parallel direction index. - if (gks->lower_bc[par_dir].type == GKYL_BC_GK_SPECIES_TWISTSHIFT && - gks->upper_bc[par_dir].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { - // Release objects used for TS BCs. - gkyl_array_release(gks->bc_ts_buffer_fine); - gkyl_array_release(gks->bc_ts_buffer_coar); - gkyl_dg_interpolate_release(gks->bc_ts_prolong); - gkyl_dg_interpolate_release(gks->bc_ts_coarsen); - } // Copy BCs are allocated by default. Need to free. for (int d=0; dcdim; ++d) { @@ -932,63 +882,15 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app if (gks->lower_bc[par_dir].type == GKYL_BC_GK_SPECIES_TWISTSHIFT && gks->upper_bc[par_dir].type == GKYL_BC_GK_SPECIES_TWISTSHIFT) { - // Initialize an interpolation operator on a higher resolution grid, with - // twice as many cells along x and y. - int ts_cells[GKYL_MAX_DIM]; - for (int d=0; dgrid.cells[d]; - } - for (int d=-1; dgrid.cells[cdim+d]; - } - gkyl_rect_grid_init(&gks->bc_ts_grid, pdim, gks->grid.lower, gks->grid.upper, ts_cells); - - struct gkyl_range bc_ts_global_ext, bc_ts_global; - gkyl_create_grid_ranges(&gks->bc_ts_grid, num_ghost, &bc_ts_global_ext, &bc_ts_global); - - // Create a communicator. - int bc_ts_cuts[GKYL_MAX_DIM] = {0}; - gkyl_rect_decomp_get_cuts(app->decomp, bc_ts_cuts); - // Set velocity space cuts to 1 as we do not use MPI in vel-space. - for (int d=0; dcomm, 0, bc_ts_decomp); - - // Local range. - int my_rank = 0; - gkyl_comm_get_rank(bc_ts_comm, &my_rank); - - gkyl_create_ranges(&bc_ts_decomp->ranges[my_rank], num_ghost, &gks->bc_ts_local_ext, &gks->bc_ts_local); - - // Skin and ghost ranges for configuration space fields. - gkyl_skin_ghost_ranges(&gks->bc_ts_local_lower_skin_par, &gks->bc_ts_local_lower_ghost_par, - par_dir, GKYL_LOWER_EDGE, &gks->bc_ts_local_ext, num_ghost); - gkyl_skin_ghost_ranges(&gks->bc_ts_local_upper_skin_par, &gks->bc_ts_local_upper_ghost_par, - par_dir, GKYL_UPPER_EDGE, &gks->bc_ts_local_ext, num_ghost); - - gks->bc_ts_prolong = gkyl_dg_interpolate_new(app->cdim, &gks->basis, - &gks->grid, &gks->bc_ts_grid, &gks->local, &gks->bc_ts_local, num_ghost, app->use_gpu); - gks->bc_ts_coarsen = gkyl_dg_interpolate_new(app->cdim, &gks->basis, - &gks->bc_ts_grid, &gks->grid, &gks->bc_ts_local, &gks->local, num_ghost, app->use_gpu); - - gks->bc_ts_buffer_fine = mkarr(app->use_gpu, gks->basis.num_basis, gks->bc_ts_local_ext.volume); - gks->bc_ts_buffer_coar = mkarr(app->use_gpu, gks->basis.num_basis, gks->local_ext.volume); - - // Create a local range extended in the BC dir. + // Local range extended in the BC dir, on the coarse grid. int lower_bcdir_ext[pdim], upper_bcdir_ext[pdim]; for (int i=0; ibc_ts_local.lower[i]; - upper_bcdir_ext[i] = gks->bc_ts_local.upper[i]; + lower_bcdir_ext[i] = gks->local.lower[i]; + upper_bcdir_ext[i] = gks->local.upper[i]; } - lower_bcdir_ext[par_dir] = gks->bc_ts_local_ext.lower[par_dir]; - upper_bcdir_ext[par_dir] = gks->bc_ts_local_ext.upper[par_dir]; - gkyl_sub_range_init(&gks->bc_ts_local_par_ext, &gks->bc_ts_local_ext, lower_bcdir_ext, upper_bcdir_ext); - - gkyl_rect_decomp_release(bc_ts_decomp); - gkyl_comm_release(bc_ts_comm); + lower_bcdir_ext[par_dir] = gks->local_ext.lower[par_dir]; + upper_bcdir_ext[par_dir] = gks->local_ext.upper[par_dir]; + gkyl_sub_range_init(&gks->bc_ts_local_par_ext, &gks->local_ext, lower_bcdir_ext, upper_bcdir_ext); } for (int d=0; dlocal_par_ext, .bcdir_ext_update_r = gks->bc_ts_local_par_ext, .num_ghost = num_ghost, .basis = gks->basis, -// .grid = gks->grid, - .grid = gks->bc_ts_grid, + .grid = gks->grid, .use_gpu = app->use_gpu, + .upsample_factor = app->ts_upsample_factor, + .filter_half_width = app->ts_filter_half_width, + .filter_cutoff_wavelength = app->ts_filter_cutoff_wavelength, }; if (app->gk_geom->geometry_id == GKYL_GEOMETRY_TOKAMAK) tsinp.shift_dg = app->delta_ts_x_lo; @@ -1069,13 +972,14 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app .shear_dir = 0, // shift varies with x. .edge = GKYL_UPPER_EDGE, .cdim = cdim, -// .bcdir_ext_update_r = gks->local_par_ext, .bcdir_ext_update_r = gks->bc_ts_local_par_ext, .num_ghost = num_ghost, .basis = gks->basis, -// .grid = gks->grid, - .grid = gks->bc_ts_grid, + .grid = gks->grid, .use_gpu = app->use_gpu, + .upsample_factor = app->ts_upsample_factor, + .filter_half_width = app->ts_filter_half_width, + .filter_cutoff_wavelength = app->ts_filter_cutoff_wavelength, }; if (app->gk_geom->geometry_id == GKYL_GEOMETRY_TOKAMAK) tsinp.shift_dg = app->delta_ts_x_up; diff --git a/gyrokinetic/apps/gkyl_gyrokinetic.h b/gyrokinetic/apps/gkyl_gyrokinetic.h index e04e1510ba..fe209839d2 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic.h @@ -246,6 +246,11 @@ struct gkyl_gyrokinetic_geometry { void *parallel_lower_bc_shift_ctx; // Context for parallel_lower_bc_shift_func. void *parallel_upper_bc_shift_ctx; // Context for parallel_upper_bc_shift_func. + // Twist-shift anti-aliasing filter. + int ts_upsample_factor; // Supersampling factor along x and y. + int ts_filter_half_width; // Filter stencil half-width in fine cells. + double ts_filter_cutoff_wavelength; // Filter cutoff wavelength. + struct gkyl_efit_inp efit_info; // Context with RZ data such as efit file for a tokamak or mirror. struct gkyl_tok_geo_grid_inp tok_grid_info; // Context for tokamak geometry with computational domain info. struct gkyl_mirror_geo_grid_inp mirror_grid_info; // Context for mirror geometry with computational domain info. diff --git a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h index 66d0670e6a..b3bf246d57 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h @@ -1433,6 +1433,9 @@ struct gkyl_gyrokinetic_app { int cdim; // Configuration space dimensions. int poly_order; // Polynomial order. + int ts_upsample_factor; // Twist-shift supersampling factor (0/1 = none). + int ts_filter_half_width; // Twist-shift filter half-width (0 = off). + double ts_filter_cutoff_wavelength; // Twist-shift filter cutoff wavelength. double tcurr; // Current time. double cfl; // CFL number. double cfl_omegaH; // CFL number used for omega_H. diff --git a/gyrokinetic/apps/gyrokinetic.c b/gyrokinetic/apps/gyrokinetic.c index 4b907cb034..91e2480d62 100644 --- a/gyrokinetic/apps/gyrokinetic.c +++ b/gyrokinetic/apps/gyrokinetic.c @@ -171,6 +171,9 @@ gkyl_gyrokinetic_app_new_geom(struct gkyl_gk *gk) int cdim = app->cdim = gk->cdim; int poly_order = app->poly_order = gk->poly_order; + app->ts_upsample_factor = gk->geometry.ts_upsample_factor; + app->ts_filter_half_width = gk->geometry.ts_filter_half_width; + app->ts_filter_cutoff_wavelength = gk->geometry.ts_filter_cutoff_wavelength; int ns = app->num_species = gk->num_species; int neuts = app->num_neut_species = gk->num_neut_species; diff --git a/gyrokinetic/zero/bc_twistshift.c b/gyrokinetic/zero/bc_twistshift.c index 6bd7ffac6c..59b74254cd 100644 --- a/gyrokinetic/zero/bc_twistshift.c +++ b/gyrokinetic/zero/bc_twistshift.c @@ -1728,9 +1728,33 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) up->shear_dir = inp->shear_dir; up->edge = inp->edge; up->basis = inp->basis; - up->grid = inp->grid; up->use_gpu = inp->use_gpu; - up->local_bcdir_ext_r = inp->bcdir_ext_update_r; + + const int ndim = inp->bcdir_ext_update_r.ndim; + // Check that it is being used for 3D or 5D. + assert(ndim == 3 || ndim == 5); + + // Supersampling setup. + up->upsample = inp->filter_half_width > 0 && inp->upsample_factor > 1; + if (up->upsample) { + int fine_cells[GKYL_MAX_DIM]; + for (int d=0; dgrid.cells[d]; + fine_cells[up->shift_dir] *= inp->upsample_factor; + fine_cells[up->shear_dir] *= inp->upsample_factor; + gkyl_rect_grid_init(&up->grid, ndim, inp->grid.lower, inp->grid.upper, fine_cells); + + struct gkyl_range fine_ext, fine_local; + gkyl_create_grid_ranges(&up->grid, inp->num_ghost, &fine_ext, &fine_local); + int flo[GKYL_MAX_DIM], fup[GKYL_MAX_DIM]; + for (int d=0; dbc_dir] = fine_ext.lower[up->bc_dir]; + fup[up->bc_dir] = fine_ext.upper[up->bc_dir]; + gkyl_sub_range_init(&up->local_bcdir_ext_r, &fine_ext, flo, fup); + } + else { + up->grid = inp->grid; + up->local_bcdir_ext_r = inp->bcdir_ext_update_r; + } // Assume the poly order of the DG shift is the same as that of the field, // unless requested otherwise. @@ -1738,19 +1762,14 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) if (inp->shift_poly_order) up->shift_poly_order = inp->shift_poly_order; - const int ndim = inp->bcdir_ext_update_r.ndim; - // Check that it is being used for 3D or 5D. Likely only small changes are - // needed to make it work in other dimensions. - assert(ndim == 3 || ndim == 5); - double lo1d[1], up1d[1]; int cells1d[1]; // Create 1D grid and range in the direction of the shear. gkyl_range_init(&up->shear_r, 1, (int[]) {up->local_bcdir_ext_r.lower[inp->shear_dir]}, (int[]) {up->local_bcdir_ext_r.upper[inp->shear_dir]}); - lo1d[0] = inp->grid.lower[up->shear_dir]; - up1d[0] = inp->grid.upper[up->shear_dir]; - cells1d[0] = inp->grid.cells[up->shear_dir]; + lo1d[0] = up->grid.lower[up->shear_dir]; + up1d[0] = up->grid.upper[up->shear_dir]; + cells1d[0] = up->grid.cells[up->shear_dir]; gkyl_rect_grid_init(&up->shear_grid, 1, lo1d, up1d, cells1d); int idx[] = {up->shear_r.lower[0]}; long linidx = gkyl_range_idx(&up->shear_r, idx); @@ -1758,9 +1777,9 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) // Create 1D grid and range in the diretion of the shift. gkyl_range_init(&up->shift_r, 1, (int[]) {up->local_bcdir_ext_r.lower[inp->shift_dir]}, (int[]) {up->local_bcdir_ext_r.upper[inp->shift_dir]}); - lo1d[0] = inp->grid.lower[up->shift_dir]; - up1d[0] = inp->grid.upper[up->shift_dir]; - cells1d[0] = inp->grid.cells[up->shift_dir]; + lo1d[0] = up->grid.lower[up->shift_dir]; + up1d[0] = up->grid.upper[up->shift_dir]; + cells1d[0] = up->grid.cells[up->shift_dir]; gkyl_rect_grid_init(&up->shift_grid, 1, lo1d, up1d, cells1d); // Create 2D grid (and range) the twist-shift takes place in. @@ -1779,9 +1798,9 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) } gkyl_range_init(&up->ts_r, 2, (int[]) {up->local_bcdir_ext_r.lower[dimlo], up->local_bcdir_ext_r.lower[dimup]}, (int[]) {up->local_bcdir_ext_r.upper[dimlo], up->local_bcdir_ext_r.upper[dimup]}); - double lo2d[] = {inp->grid.lower[dimlo], inp->grid.lower[dimup]}; - double up2d[] = {inp->grid.upper[dimlo], inp->grid.upper[dimup]}; - int cells2d[] = {inp->grid.cells[dimlo], inp->grid.cells[dimup]}; + double lo2d[] = {up->grid.lower[dimlo], up->grid.lower[dimup]}; + double up2d[] = {up->grid.upper[dimlo], up->grid.upper[dimup]}; + int cells2d[] = {up->grid.cells[dimlo], up->grid.cells[dimup]}; gkyl_rect_grid_init(&up->ts_grid, 2, lo2d, up2d, cells2d); // Project the shift onto the shift basis. @@ -1908,20 +1927,50 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) else gkyl_range_shorten_from_below(&up->ghost_r, &up->local_bcdir_ext_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); + // Optional low-pass filter along shear_dir to de-alias the shifted field. + up->filter = NULL; + up->filter_buff = NULL; + if (inp->filter_half_width > 0) + up->filter = gkyl_dg_lowpass_filter_new(up->shear_dir, inp->filter_half_width, + inp->filter_cutoff_wavelength, &up->basis, &up->grid, &up->ghost_r, up->use_gpu); + + // Set up prolongation/coarsening between the coarse and fine grids. + up->prolong = NULL; + up->coarsen = NULL; + up->fine_buff = NULL; + up->coarse_buff = NULL; + if (up->upsample) { + struct gkyl_range coarse_ext, coarse_local, fine_ext, fine_local; + gkyl_create_grid_ranges(&inp->grid, inp->num_ghost, &coarse_ext, &coarse_local); + gkyl_create_grid_ranges(&up->grid, inp->num_ghost, &fine_ext, &fine_local); + + struct gkyl_range opp_ghost, this_ghost_c, this_ghost_f, opp_skin_c; + enum gkyl_edge_loc opp = inp->edge == GKYL_LOWER_EDGE? GKYL_UPPER_EDGE : GKYL_LOWER_EDGE; + gkyl_skin_ghost_ranges(&up->coarse_this_skin, &this_ghost_c, inp->bc_dir, inp->edge, &coarse_ext, inp->num_ghost); + gkyl_skin_ghost_ranges(&up->fine_this_skin, &this_ghost_f, inp->bc_dir, inp->edge, &fine_ext, inp->num_ghost); + gkyl_skin_ghost_ranges(&opp_skin_c, &opp_ghost, inp->bc_dir, opp, &fine_ext, inp->num_ghost); + up->coarse_this_ghost = this_ghost_c; + up->fine_this_ghost = this_ghost_f; + up->fine_opp_skin = opp_skin_c; + + // Interpolate the interior only (periodicity re-derives the fine ghost); + // this also avoids refining coarse ghost cells, which dg_interpolate maps + // out of bounds for refinement factors > 2. + up->prolong = gkyl_dg_interpolate_new(inp->cdim, &up->basis, &inp->grid, &up->grid, + &coarse_local, &fine_local, inp->num_ghost, up->use_gpu); + up->coarsen = gkyl_dg_interpolate_new(inp->cdim, &up->basis, &up->grid, &inp->grid, + &fine_local, &coarse_local, inp->num_ghost, up->use_gpu); + + up->fine_buff = gkyl_array_new(GKYL_DOUBLE, up->basis.num_basis, fine_ext.volume); + up->coarse_buff = gkyl_array_new(GKYL_DOUBLE, up->basis.num_basis, coarse_ext.volume); + } + return up; } -void -gkyl_bc_twistshift_advance(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar) +static void +bc_twistshift_advance_core(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar) { - -#ifdef GKYL_HAVE_CUDA - if (up->use_gpu) { - gkyl_bc_twistshift_advance_cu(up, fdo, ftar); - return; - } -#endif - // Assign the distribution matrices. // This assumes that fdo->ncomp = fmat->nr. // Recall: @@ -1982,6 +2031,39 @@ gkyl_bc_twistshift_advance(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo } } } + + // Low-pass filter the shifted ghost field. + if (up->filter) { + if (up->filter_buff == NULL) + up->filter_buff = gkyl_array_new(GKYL_DOUBLE, ftar->ncomp, ftar->size); + gkyl_array_copy_range(up->filter_buff, ftar, &up->ghost_r); + gkyl_dg_lowpass_filter_advance(up->filter, up->filter_buff, ftar); + } +} + +void +gkyl_bc_twistshift_advance(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar) +{ +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + gkyl_bc_twistshift_advance_cu(up, fdo, ftar); + return; + } +#endif + + if (up->upsample) { + // Prolong to the fine grid, apply periodicity, shift+filter, coarsen, and + // copy the coarsened skin into ftar's ghost. + gkyl_dg_interpolate_advance(up->prolong, fdo, up->fine_buff); + gkyl_array_copy_range_to_range(up->fine_buff, up->fine_buff, &up->fine_this_ghost, &up->fine_opp_skin); + bc_twistshift_advance_core(up, up->fine_buff, up->fine_buff); + gkyl_array_copy_range_to_range(up->fine_buff, up->fine_buff, &up->fine_this_skin, &up->fine_this_ghost); + gkyl_dg_interpolate_advance(up->coarsen, up->fine_buff, up->coarse_buff); + gkyl_array_copy_range_to_range(ftar, up->coarse_buff, &up->coarse_this_ghost, &up->coarse_this_skin); + } + else { + bc_twistshift_advance_core(up, fdo, ftar); + } } struct gkyl_array* @@ -2019,6 +2101,19 @@ gkyl_bc_twistshift_release(struct gkyl_bc_twistshift *up) { gkyl_array_release(up->shift_dg); + if (up->filter) { + gkyl_dg_lowpass_filter_release(up->filter); + if (up->filter_buff) + gkyl_array_release(up->filter_buff); + } + + if (up->upsample) { + gkyl_dg_interpolate_release(up->prolong); + gkyl_dg_interpolate_release(up->coarsen); + gkyl_array_release(up->fine_buff); + gkyl_array_release(up->coarse_buff); + } + gkyl_free(up->num_do); gkyl_free(up->shift_dir_idx_do); diff --git a/gyrokinetic/zero/gkyl_bc_twistshift.h b/gyrokinetic/zero/gkyl_bc_twistshift.h index 54b5871c02..c2a39c726a 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift.h @@ -26,6 +26,9 @@ struct gkyl_bc_twistshift_inp { bool use_gpu; // Whether to apply the BC using the GPU. // Optional inputs: int shift_poly_order; // Basis order for the DG representation of the shift. + int filter_half_width; // Filter stencil half-width M in cells. + double filter_cutoff_wavelength; // Filter cutoff wavelength. + int upsample_factor; // Supersampling factor. }; /** diff --git a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h index f66ffe186d..a0a6b68147 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include #include @@ -150,6 +152,18 @@ struct gkyl_bc_twistshift { struct gkyl_range permutted_ghost_r; // Ghost range to populate in the target // field, with some dimensions permutted. struct gkyl_range ghost_r; // Ghost range this BC fills. + + struct gkyl_dg_lowpass_filter *filter; // Post-shift filter. + struct gkyl_array *filter_buff; // Scratch buffer for the filter. + + bool upsample; // Whether to upsample before the shift. + struct gkyl_dg_interpolate *prolong; // Coarse to fine operator. + struct gkyl_dg_interpolate *coarsen; // Fine to coarse operator. + struct gkyl_array *fine_buff; // Field on the fine grid. + struct gkyl_array *coarse_buff; // Field on the coarse grid. + struct gkyl_range fine_this_skin, fine_this_ghost; // Fine skin/ghost at this edge. + struct gkyl_range fine_opp_skin; // Fine skin at the opposite edge (periodicity donor). + struct gkyl_range coarse_this_skin, coarse_this_ghost; // Coarse skin/ghost at this edge. }; #ifdef GKYL_HAVE_CUDA From 49b773d87fada60293bf2bbceeb3cbdc855402de Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Mon, 27 Jul 2026 11:27:08 -0400 Subject: [PATCH 07/30] The granularity of the twist and shift updater is increased. The bc_twistshift is a boundary condition operator that runs now the whole process of the boundary condition, i.e. periodic -> upsample -> twist shift -> filter -> downsample. A new updater twistshift_dg is created to contain the "pure" twist-shift operations. It compiles but some program flow errors may remain in this commit and the GPU path is not ready yet. --- gyrokinetic/Makefile-gyrokinetic | 2 +- gyrokinetic/apps/gk_field_2x3x.c | 21 +- gyrokinetic/apps/gkyl_gyrokinetic_priv.h | 1 + gyrokinetic/apps/gyrokinetic.c | 8 +- gyrokinetic/gyrokineticlinkobjs.mak | 2 +- .../gkyl_twistshift_dg_gyrokinetic_kernels.h} | 0 .../twistshift_dg_gyrokinetic_ser_2x_p1.c} | 2 +- .../twistshift_dg_gyrokinetic_ser_3x2v_p1.c} | 2 +- .../twistshift_dg_gyrokinetic_ser_3x_p1.c} | 2 +- ..._bc_twistshift.c => ctest_twistshift_dg.c} | 46 +- gyrokinetic/zero/bc_twistshift.c | 2187 +---------------- gyrokinetic/zero/gkyl_bc_twistshift.h | 42 +- gyrokinetic/zero/gkyl_bc_twistshift_priv.h | 193 +- gyrokinetic/zero/gkyl_twistshift_dg.h | 68 + gyrokinetic/zero/gkyl_twistshift_dg_priv.h | 165 ++ gyrokinetic/zero/twistshift_dg.c | 2026 +++++++++++++++ ...c_twistshift_cu.cu => twistshift_dg_cu.cu} | 14 +- 17 files changed, 2466 insertions(+), 2315 deletions(-) rename gyrokinetic/ker/{twistshift/gkyl_bc_twistshift_gyrokinetic_kernels.h => twistshift_dg/gkyl_twistshift_dg_gyrokinetic_kernels.h} (100%) rename gyrokinetic/ker/{twistshift/bc_twistshift_gyrokinetic_ser_2x_p1.c => twistshift_dg/twistshift_dg_gyrokinetic_ser_2x_p1.c} (99%) rename gyrokinetic/ker/{twistshift/bc_twistshift_gyrokinetic_ser_3x2v_p1.c => twistshift_dg/twistshift_dg_gyrokinetic_ser_3x2v_p1.c} (99%) rename gyrokinetic/ker/{twistshift/bc_twistshift_gyrokinetic_ser_3x_p1.c => twistshift_dg/twistshift_dg_gyrokinetic_ser_3x_p1.c} (99%) rename gyrokinetic/unit/{ctest_bc_twistshift.c => ctest_twistshift_dg.c} (98%) create mode 100644 gyrokinetic/zero/gkyl_twistshift_dg.h create mode 100644 gyrokinetic/zero/gkyl_twistshift_dg_priv.h create mode 100644 gyrokinetic/zero/twistshift_dg.c rename gyrokinetic/zero/{bc_twistshift_cu.cu => twistshift_dg_cu.cu} (89%) diff --git a/gyrokinetic/Makefile-gyrokinetic b/gyrokinetic/Makefile-gyrokinetic index ccf1c33622..2688a4695b 100644 --- a/gyrokinetic/Makefile-gyrokinetic +++ b/gyrokinetic/Makefile-gyrokinetic @@ -175,7 +175,7 @@ ifdef USING_NVCC $(MKDIR_P) $(dir $@) $(CC) $(CFLAGS) $(NVCC_FLAGS) $(INCS) -c $< -o $@ -../$(BUILD_DIR)/gyrokinetic/$(KERNELS_DIR)/twistshift/%.c.o : $(KERNELS_DIR)/twistshift/%.c +../$(BUILD_DIR)/gyrokinetic/$(KERNELS_DIR)/twistshift_dg/%.c.o : $(KERNELS_DIR)/twistshift_dg/%.c $(MKDIR_P) $(dir $@) $(CC) $(CFLAGS) $(NVCC_FLAGS) $(INCS) -c $< -o $@ diff --git a/gyrokinetic/apps/gk_field_2x3x.c b/gyrokinetic/apps/gk_field_2x3x.c index 77a114ae50..2ee15a67ad 100644 --- a/gyrokinetic/apps/gk_field_2x3x.c +++ b/gyrokinetic/apps/gk_field_2x3x.c @@ -49,14 +49,6 @@ gk_field_fem_projection_par_phi_ts_2x(gkyl_gyrokinetic_app *app, struct gk_field gkyl_array_copy_range_to_range(arr_fem, field->phi_fem, &app->local, &field->global_sub_range); } -static void -gk_field_apply_ts_bc_interp(gkyl_gyrokinetic_app *app, struct gk_field *gkf, - struct gkyl_array *fin, struct gkyl_array *fout) -{ - // Apply the twist-shift BC in the lower parallel ghost. - gkyl_bc_twistshift_advance(gkf->bc_ts_lo, fin, fout); -} - static void gk_field_fem_projection_par_phi_ts_3x(gkyl_gyrokinetic_app *app, struct gk_field *field, struct gkyl_array *arr_dg, struct gkyl_array *arr_fem) @@ -69,11 +61,7 @@ gk_field_fem_projection_par_phi_ts_3x(gkyl_gyrokinetic_app *app, struct gk_field gkyl_comm_array_allgather(app->comm, &app->local, &app->global, arr_dg, field->rho_c_global_dg); // Apply TS BC in the lower parallel boundary. - gk_field_apply_ts_bc_interp(app, field, field->rho_c_global_dg, field->rho_c_global_dg); -// int par_dir = app->cdim-1; // Parallel direction index. -// gkyl_array_copy_range_to_range(field->rho_c_global_dg, field->rho_c_global_dg, -// &app->global_lower_ghost[par_dir], &app->global_upper_skin[par_dir]); -// gkyl_bc_twistshift_advance(field->bc_ts_lo, field->rho_c_global_dg, field->rho_c_global_dg); + gkyl_bc_twistshift_advance(field->bc_ts_lo, field->rho_c_global_dg, field->rho_c_global_dg); // Fill upper parallel boundary ghost with skin boundary value. gkyl_bc_basic_gyrokinetic_advance(field->gfss_bc_op_core_up, field->bc_buffer, field->rho_c_global_dg); @@ -136,10 +124,7 @@ gk_field_fem_projection_par_phi_iwl_3x(gkyl_gyrokinetic_app *app, struct gk_fiel // Gather the DG array into a global (in z) array. gkyl_comm_array_allgather(app->comm, &app->local, &app->global, arr_dg, field->rho_c_global_dg); - // Apply TS BC in the core lower parallel boundary, and - // fill core upper parallel boundary ghost with skin boundary value. - gkyl_array_copy_range_to_range(field->rho_c_global_dg, field->rho_c_global_dg, - &app->global_lower_ghost_par_core, &app->global_upper_skin_par_core); + // Apply TS BC in the core lower parallel boundary. gkyl_bc_twistshift_advance(field->bc_ts_lo, field->rho_c_global_dg, field->rho_c_global_dg); gkyl_bc_basic_gyrokinetic_advance(field->gfss_bc_op_core_up, field->bc_buffer, field->rho_c_global_dg); @@ -378,6 +363,8 @@ gk_field_2x3x_add_IWL_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field .basis = app->basis, .grid = app->grid, .use_gpu = app->use_gpu, + .periodic_in_r = &app->global_lower_ghost_par_core, + .periodic_out_r = &app->global_upper_skin_par_core }; if (app->gk_geom->geometry_id == GKYL_GEOMETRY_TOKAMAK) T_LU_lo.shift_dg = app->delta_ts_x_lo; diff --git a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h index b3bf246d57..2d67740b82 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic_priv.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic_priv.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/gyrokinetic/apps/gyrokinetic.c b/gyrokinetic/apps/gyrokinetic.c index 91e2480d62..979338e46e 100644 --- a/gyrokinetic/apps/gyrokinetic.c +++ b/gyrokinetic/apps/gyrokinetic.c @@ -1247,7 +1247,7 @@ gyrokinetic_app_write_ts_shift_mapc2p(struct gkyl_gyrokinetic_app *app) for (int eI = 0; eI < 2; eI++) { int ghost[] = {1, 1, 1}; // TS BC updater. - struct gkyl_bc_twistshift_inp ts_inp = { + struct gkyl_twistshift_dg_inp ts_inp = { .bc_dir = par_dir, .shift_dir = 1, // y shift. .shear_dir = 0, // shift varies with x. @@ -1261,10 +1261,10 @@ gyrokinetic_app_write_ts_shift_mapc2p(struct gkyl_gyrokinetic_app *app) .shift_func_ctx = eI == 0? app->gk_geom->parallel_lower_bc_shift_ctx : app->gk_geom->parallel_upper_bc_shift_ctx, .use_gpu = app->use_gpu, }; - struct gkyl_bc_twistshift *bc_ts_op = gkyl_bc_twistshift_new(&ts_inp); + struct gkyl_twistshift_dg *bc_ts_op = gkyl_twistshift_dg_new(&ts_inp); struct gkyl_array *delta_ts_x = eI == 0? app->delta_ts_x_lo : app->delta_ts_x_up; - delta_ts_x = gkyl_bc_twistshift_get_shift_objects(bc_ts_op, + delta_ts_x = gkyl_twistshift_dg_get_shift_objects(bc_ts_op, &app->delta_ts_x_grid, &app->delta_ts_x_rng, &app->delta_ts_x_basis); bool has_LCFS = app->gk_geom->has_LCFS; @@ -1310,7 +1310,7 @@ gyrokinetic_app_write_ts_shift_mapc2p(struct gkyl_gyrokinetic_app *app) gkyl_array_release(delta_ts_x); gkyl_msgpack_data_release(mt_shift); - gkyl_bc_twistshift_release(bc_ts_op); + gkyl_twistshift_dg_release(bc_ts_op); } } diff --git a/gyrokinetic/gyrokineticlinkobjs.mak b/gyrokinetic/gyrokineticlinkobjs.mak index f5a1de13e5..01af73ed6c 100644 --- a/gyrokinetic/gyrokineticlinkobjs.mak +++ b/gyrokinetic/gyrokineticlinkobjs.mak @@ -1,7 +1,7 @@ # -*- makefile-gmake -*- # Gyrokinetic include objects -GYROKINETIC_INCS = -I../gyrokinetic/$(KERNELS_DIR)/ambi_bolt_potential -I../gyrokinetic/$(KERNELS_DIR)/bgk_gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/deflate_geo -I../gyrokinetic/$(KERNELS_DIR)/deflate_surf -I../gyrokinetic/$(KERNELS_DIR)/derived_geo -I../gyrokinetic/$(KERNELS_DIR)/dg_diffusion_gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/fem_parproj -I../gyrokinetic/$(KERNELS_DIR)/fem_poisson_perp -I../gyrokinetic/$(KERNELS_DIR)/gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/gyrokinetic_pol_density -I../gyrokinetic/$(KERNELS_DIR)/inflate_surf -I../gyrokinetic/$(KERNELS_DIR)/lbo_gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/neutral -I../gyrokinetic/$(KERNELS_DIR)/positivity_shift_gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/rad -I../gyrokinetic/$(KERNELS_DIR)/translate_dim -I../gyrokinetic/$(KERNELS_DIR)/twistshift -I../gyrokinetic/apps -I../gyrokinetic/zero +GYROKINETIC_INCS = -I../gyrokinetic/$(KERNELS_DIR)/ambi_bolt_potential -I../gyrokinetic/$(KERNELS_DIR)/bgk_gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/deflate_geo -I../gyrokinetic/$(KERNELS_DIR)/deflate_surf -I../gyrokinetic/$(KERNELS_DIR)/derived_geo -I../gyrokinetic/$(KERNELS_DIR)/dg_diffusion_gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/fem_parproj -I../gyrokinetic/$(KERNELS_DIR)/fem_poisson_perp -I../gyrokinetic/$(KERNELS_DIR)/gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/gyrokinetic_pol_density -I../gyrokinetic/$(KERNELS_DIR)/inflate_surf -I../gyrokinetic/$(KERNELS_DIR)/lbo_gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/neutral -I../gyrokinetic/$(KERNELS_DIR)/positivity_shift_gyrokinetic -I../gyrokinetic/$(KERNELS_DIR)/rad -I../gyrokinetic/$(KERNELS_DIR)/translate_dim -I../gyrokinetic/$(KERNELS_DIR)/twistshift_dg -I../gyrokinetic/apps -I../gyrokinetic/zero # Gyrokinetic link objects diff --git a/gyrokinetic/ker/twistshift/gkyl_bc_twistshift_gyrokinetic_kernels.h b/gyrokinetic/ker/twistshift_dg/gkyl_twistshift_dg_gyrokinetic_kernels.h similarity index 100% rename from gyrokinetic/ker/twistshift/gkyl_bc_twistshift_gyrokinetic_kernels.h rename to gyrokinetic/ker/twistshift_dg/gkyl_twistshift_dg_gyrokinetic_kernels.h diff --git a/gyrokinetic/ker/twistshift/bc_twistshift_gyrokinetic_ser_2x_p1.c b/gyrokinetic/ker/twistshift_dg/twistshift_dg_gyrokinetic_ser_2x_p1.c similarity index 99% rename from gyrokinetic/ker/twistshift/bc_twistshift_gyrokinetic_ser_2x_p1.c rename to gyrokinetic/ker/twistshift_dg/twistshift_dg_gyrokinetic_ser_2x_p1.c index 40b5c81b90..e0b0111610 100644 --- a/gyrokinetic/ker/twistshift/bc_twistshift_gyrokinetic_ser_2x_p1.c +++ b/gyrokinetic/ker/twistshift_dg/twistshift_dg_gyrokinetic_ser_2x_p1.c @@ -1,4 +1,4 @@ -#include +#include GKYL_CU_DH void twistshift_xlimdg_2x_ser_p1_yshift_p1(double sFac, const double *xLimLo, const double *xLimUp, double yLimLo, double yLimUp, double dyDo, double yOff, const double *ySh, struct gkyl_mat *tsmat) { diff --git a/gyrokinetic/ker/twistshift/bc_twistshift_gyrokinetic_ser_3x2v_p1.c b/gyrokinetic/ker/twistshift_dg/twistshift_dg_gyrokinetic_ser_3x2v_p1.c similarity index 99% rename from gyrokinetic/ker/twistshift/bc_twistshift_gyrokinetic_ser_3x2v_p1.c rename to gyrokinetic/ker/twistshift_dg/twistshift_dg_gyrokinetic_ser_3x2v_p1.c index 623502ca03..8e191a45dc 100644 --- a/gyrokinetic/ker/twistshift/bc_twistshift_gyrokinetic_ser_3x2v_p1.c +++ b/gyrokinetic/ker/twistshift_dg/twistshift_dg_gyrokinetic_ser_3x2v_p1.c @@ -1,4 +1,4 @@ -#include +#include GKYL_CU_DH void twistshift_xlimdg_3x2v_ser_p1_yshift_p1(double sFac, const double *xLimLo, const double *xLimUp, double yLimLo, double yLimUp, double dyDo, double yOff, const double *ySh, struct gkyl_mat *tsmat) { diff --git a/gyrokinetic/ker/twistshift/bc_twistshift_gyrokinetic_ser_3x_p1.c b/gyrokinetic/ker/twistshift_dg/twistshift_dg_gyrokinetic_ser_3x_p1.c similarity index 99% rename from gyrokinetic/ker/twistshift/bc_twistshift_gyrokinetic_ser_3x_p1.c rename to gyrokinetic/ker/twistshift_dg/twistshift_dg_gyrokinetic_ser_3x_p1.c index 3c370244ae..ba3345a92f 100644 --- a/gyrokinetic/ker/twistshift/bc_twistshift_gyrokinetic_ser_3x_p1.c +++ b/gyrokinetic/ker/twistshift_dg/twistshift_dg_gyrokinetic_ser_3x_p1.c @@ -1,4 +1,4 @@ -#include +#include GKYL_CU_DH void twistshift_xlimdg_3x_ser_p1_yshift_p1(double sFac, const double *xLimLo, const double *xLimUp, double yLimLo, double yLimUp, double dyDo, double yOff, const double *ySh, struct gkyl_mat *tsmat) { diff --git a/gyrokinetic/unit/ctest_bc_twistshift.c b/gyrokinetic/unit/ctest_twistshift_dg.c similarity index 98% rename from gyrokinetic/unit/ctest_bc_twistshift.c rename to gyrokinetic/unit/ctest_twistshift_dg.c index 47319fb346..695a6bce8d 100644 --- a/gyrokinetic/unit/ctest_bc_twistshift.c +++ b/gyrokinetic/unit/ctest_twistshift_dg.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include @@ -318,7 +318,7 @@ test_bc_twistshift_3x_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_sub_range_init(&update_rng, &local_ext, lower_bcdir_ext, upper_bcdir_ext); // Create the twist-shift updater and shift the donor field. - struct gkyl_bc_twistshift_inp tsinp = { + struct gkyl_twistshift_dg_inp tsinp = { .bc_dir = bc_dir, .shift_dir = 1, // y shift. .shear_dir = 0, // shift varies with x. @@ -334,13 +334,13 @@ test_bc_twistshift_3x_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, .use_gpu = use_gpu, }; - struct gkyl_bc_twistshift *tsup = gkyl_bc_twistshift_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); // First apply periodicity in z. struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); apply_periodic_bc(buff_per, distf, bc_dir, skin_ghost); - gkyl_bc_twistshift_advance(tsup, distf, distf); + gkyl_twistshift_dg_advance(tsup, distf, distf); gkyl_array_copy(distf_ho, distf); // Write out the target in the extended range. @@ -391,8 +391,8 @@ test_bc_twistshift_3x_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_array_copy_range_to_range(distf, distf, &skin_rng, &ghost_rng); tsinp.shift_func = shift1m_fig6; // tsinp.shift_func = shiftm_fig9; - struct gkyl_bc_twistshift *tsup_m = gkyl_bc_twistshift_new(&tsinp); - gkyl_bc_twistshift_advance(tsup_m, distf, distf); + struct gkyl_twistshift_dg *tsup_m = gkyl_twistshift_dg_new(&tsinp); + gkyl_twistshift_dg_advance(tsup_m, distf, distf); gkyl_array_copy(distf_ho, distf); if (write_f) { @@ -439,8 +439,8 @@ test_bc_twistshift_3x_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_array_release(buff_per); test_bc_twistshift_array_meta_release(mt); - gkyl_bc_twistshift_release(tsup); - gkyl_bc_twistshift_release(tsup_m); + gkyl_twistshift_dg_release(tsup); + gkyl_twistshift_dg_release(tsup_m); gkyl_proj_on_basis_release(projDistf); gkyl_array_release(distf_ho); gkyl_array_release(distf); @@ -575,7 +575,7 @@ test_bc_twistshift_3x2v_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_sub_range_init(&update_rng, &local_ext, lower_bcdir_ext, upper_bcdir_ext); // Create the twist-shift updater and shift the donor field. - struct gkyl_bc_twistshift_inp tsinp = { + struct gkyl_twistshift_dg_inp tsinp = { .bc_dir = bc_dir, .shift_dir = 1, // y shift. .shear_dir = 0, // shift varies with x. @@ -591,13 +591,13 @@ test_bc_twistshift_3x2v_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, .use_gpu = use_gpu, }; - struct gkyl_bc_twistshift *tsup = gkyl_bc_twistshift_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); // First apply periodicity in z. struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); apply_periodic_bc(buff_per, distf, bc_dir, skin_ghost); - gkyl_bc_twistshift_advance(tsup, distf, distf); + gkyl_twistshift_dg_advance(tsup, distf, distf); gkyl_array_copy(distf_ho, distf); // Write out the target in the extended range. @@ -732,8 +732,8 @@ test_bc_twistshift_3x2v_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_array_copy_range_to_range(distf, distf, &skin_rng, &ghost_rng); tsinp.shift_func = shift1m_fig6; // tsinp.shift_func = shiftm_fig9; - struct gkyl_bc_twistshift *tsup_m = gkyl_bc_twistshift_new(&tsinp); - gkyl_bc_twistshift_advance(tsup_m, distf, distf); + struct gkyl_twistshift_dg *tsup_m = gkyl_twistshift_dg_new(&tsinp); + gkyl_twistshift_dg_advance(tsup_m, distf, distf); gkyl_array_copy(distf_ho, distf); if (write_f) { @@ -811,8 +811,8 @@ test_bc_twistshift_3x2v_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_velocity_map_release(gvm); gkyl_array_release(buff_per); test_bc_twistshift_array_meta_release(mt); - gkyl_bc_twistshift_release(tsup); - gkyl_bc_twistshift_release(tsup_m); + gkyl_twistshift_dg_release(tsup); + gkyl_twistshift_dg_release(tsup_m); gkyl_proj_on_basis_release(projDistf); gkyl_array_release(distf_ho); gkyl_array_release(distf); @@ -986,7 +986,7 @@ test_bc_twistshift_3x_fig11_wcells(const int *cells, enum gkyl_edge_loc edge, } // Create the twist-shift updater and shift the donor field. - struct gkyl_bc_twistshift_inp tsinp = { + struct gkyl_twistshift_dg_inp tsinp = { .bc_dir = bc_dir, .shift_dir = 1, // y shift. .shear_dir = 0, // shift varies with x. @@ -1001,13 +1001,13 @@ test_bc_twistshift_3x_fig11_wcells(const int *cells, enum gkyl_edge_loc edge, .use_gpu = use_gpu, }; - struct gkyl_bc_twistshift *tsup = gkyl_bc_twistshift_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); // First apply periodicity in z. struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); apply_periodic_bc(buff_per, distf, bc_dir, skin_ghost); - gkyl_bc_twistshift_advance(tsup, distf, distf); + gkyl_twistshift_dg_advance(tsup, distf, distf); gkyl_array_copy(distf_ho, distf); // Write out the target in the extended range. @@ -1174,7 +1174,7 @@ test_bc_twistshift_3x_fig11_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_array_release(buff_per); test_bc_twistshift_array_meta_release(mt); - gkyl_bc_twistshift_release(tsup); + gkyl_twistshift_dg_release(tsup); gkyl_proj_on_basis_release(projDistf); gkyl_array_release(distf_ho); gkyl_array_release(distf); @@ -1320,7 +1320,7 @@ test_bc_twistshift_3x2v_fig11_wcells(const int *cells, enum gkyl_edge_loc edge, } // Create the twist-shift updater and shift the donor field. - struct gkyl_bc_twistshift_inp tsinp = { + struct gkyl_twistshift_dg_inp tsinp = { .bc_dir = bc_dir, .shift_dir = 1, // y shift. .shear_dir = 0, // shift varies with x. @@ -1335,13 +1335,13 @@ test_bc_twistshift_3x2v_fig11_wcells(const int *cells, enum gkyl_edge_loc edge, .use_gpu = use_gpu, }; - struct gkyl_bc_twistshift *tsup = gkyl_bc_twistshift_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); // First apply periodicity in z. struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); apply_periodic_bc(buff_per, distf, bc_dir, skin_ghost); - gkyl_bc_twistshift_advance(tsup, distf, distf); + gkyl_twistshift_dg_advance(tsup, distf, distf); gkyl_array_copy(distf_ho, distf); // Write out the target in the extended range. @@ -1607,7 +1607,7 @@ test_bc_twistshift_3x2v_fig11_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_velocity_map_release(gvm); gkyl_array_release(buff_per); test_bc_twistshift_array_meta_release(mt); - gkyl_bc_twistshift_release(tsup); + gkyl_twistshift_dg_release(tsup); gkyl_proj_on_basis_release(projDistf); gkyl_array_release(distf_ho); gkyl_array_release(distf); diff --git a/gyrokinetic/zero/bc_twistshift.c b/gyrokinetic/zero/bc_twistshift.c index 59b74254cd..2b9f85de83 100644 --- a/gyrokinetic/zero/bc_twistshift.c +++ b/gyrokinetic/zero/bc_twistshift.c @@ -1,2121 +1,172 @@ #include #include -#include +#include #include #include -#include -#include -#include -#include - -// Notes: -// a) Hard-coded parameters: -// - wrap_to_range: eps. -// - find_donors: delta_frac, num_test_pt. -// - find_intersect: tol, max_iter, num_steps. -// - calc_mats: shift_dir_idx_tar. -// - tol_xi: Minimum allowed spacing between the lower and -// upper xi (logical x) limits of subcell integral. -// b) Unlike the procedures described in M. Francisquez, et al. CPC 298 -// (2024) 109109, all subcell integrals are now done with variable y limits. -// This is possible once we realize that figure 4 is not drawn accurately; -// the blue lines should be separated by Delta y at all points. -// c) This updater only works on 5D distributions. Likely only minor changes -// are needed to make it work in other dimensions. -// d) 99% of the code is written to support a BC, a shift and shear in any -// direction. Maybe the only thing that needs to change is the permutted -// range and its use. -// -// List of functions used in computing sub-cell integrals (scimat). -// - ts_grid_cell_boundary_in_dir: cell boundary coordinate in given dir. -// - ts_grid_cell_boundaries: get all cell boundary coords. -// - ts_p2l: physical to logical transform. -// - ts_interval_dx_and_xc: compute length and center of an interval. -// - ts_grid_length_in_dir: length of the grid in given dir. -// - ts_wrap_to_range: wrap a number to a range assuming periodicity. -// - ts_shift_dir_idx_do_linidx: linear index to first donor of a given target -// cell in shift_dir_idx_do. -// - ts_check_shifted_test_point: evaluate a shifted point's cell as a -// potential donor cell. -// - ts_find_donors: find and record the donor cells for each target. -// - ts_root_find: Finds the root of a given function. -// - ts_shifted_coord_loss_func: Loss function used to find where yTar-S -// intersects yDo. -// - ts_sign: return the sign of a double. -// - ts_ts_donor_target_offset: offset between donor and target cells. -// - ts_find_intersect: finds the intersection of yTar-S and yDo. -// - ts_comp_to_phys: transform a computational to a physical coord. -// - ts_nod2mod_proj_1d: evaluate a 1D function at nodes and do a n2m transform -// to get the coefficients of the DG representation. -// - ts_integral_xlimdg: subcell integral with variable x limits. -// - ts_integral_ylimdg: subcell integral with variable y limits. -// - ts_integral_fullcelllimdg: integral over the whole cell. -// - ts_one: return 1 (for projections). -// - ts_minus_one: return -1 (for projections). -// - ts_shift_coord_shifted_log: coordinate in shift_dir shifted and transformed -// to logical space. -// - ts_subcellint_sNi_sNii: subcell integral sNi or sNii. -// - ts_subcellint_si_sii: subcell integral si or sii. -// - ts_subcellint_siii_siv: subcell integral siii or siv. -// - ts_subcellint_sv_svi: subcell integral sv or svi. -// - ts_subcellint_svii_sviii: subcell integral svii or sviii. -// - ts_subcellint_six_sx: subcell integral six or sx. -// - ts_subcellint_sxi_sxii: subcell integral sxi or sxii. -// - ts_subcellint_sxiii_sxiv: subcell integral sxiii or sxiv. -// - ts_subcellint_sxv_sxvi: subcell integral sxv or sxvi. -// - ts_calc_mats: create scimat with the result of the subcell integrals. -// -// Two additional helper functions: -// - ts_calc_num_numcol_fidx_do: index map to populate fmat with donors. -// - ts_calc_num_numcol_fidx_tar: index map to get mat-mat mult results. - -// Option to use the user-provided function describing the shift -// or a DG representation of it: -// = 0 DG representation (default and preferred). -// = 1 user-provided shift function. -// Note: the kernels that ultimately perform the integrals -// always use the DG representation. -#define shift_func_op 0 - -// Minimum allowed spacing between the lower and -// upper xi (logical x) limits of subcell integral. -#define tol_xi 1.0e-15 - -// Indices in 4-element cell boundary array. -#define cellb_lo(dir) (2*dir) -#define cellb_up(dir) (2*dir+1) - -double -ts_grid_cell_boundary_in_dir(struct gkyl_rect_grid *grid, const int *idx, enum gkyl_edge_loc edge, int dir) -{ - // Get the coordinate of the cell boundary in specified direction. - double xc[grid->ndim]; - gkyl_rect_grid_cell_center(grid, idx, xc); - return edge == GKYL_LOWER_EDGE? xc[dir]-0.5*grid->dx[dir] : xc[dir]+0.5*grid->dx[dir]; -} - -void -ts_grid_cell_boundaries(struct gkyl_rect_grid *grid, const int *idx, double *cell_bounds) -{ - // Get the cell boundaries in every dimension. The array cell_bounds - // must be a 2*grid->ndim array. - for (int d=0; dndim; d++) { - cell_bounds[d*2] = ts_grid_cell_boundary_in_dir(grid, idx, GKYL_LOWER_EDGE, d); - cell_bounds[d*2+1] = ts_grid_cell_boundary_in_dir(grid, idx, GKYL_UPPER_EDGE, d); - } -} - -static inline double -ts_p2l(double coord, double cell_center, double dx) -{ - // Transform a physical coordinate (coord) to the [-1,1] logical - // space in a cell centered at cell_center and with length dx. - return 2.0*(coord - cell_center)/dx; -} - -// Evaluation of the shift through the DG representation. -static inline void -ts_shift_dg_eval(double t, const double *coord, double *fout, void *ctx) -{ - struct ts_shift_dg_eval_ctx *tsectx = ctx; - - int cell_idx[GKYL_MAX_DIM]; - gkyl_rect_grid_coord_idx(tsectx->shear_grid, coord, cell_idx); - // Ensure that we do not go outside of the range - // (it does sometimes if x=x_max,x_min). - cell_idx[0] = fmin(cell_idx[0], tsectx->shear_r->upper[0]); - cell_idx[0] = fmax(cell_idx[0], tsectx->shear_r->lower[0]); - - double xc[GKYL_MAX_DIM]; - gkyl_rect_grid_cell_center(tsectx->shear_grid, cell_idx, xc); - - long shift_loc = gkyl_range_idx(tsectx->shear_r, cell_idx); - double *shift_c = (double *) gkyl_array_fetch(tsectx->shift_dg, shift_loc); - double xp = ts_p2l(coord[0], xc[0], tsectx->shear_grid->dx[0]); - - fout[0] = tsectx->shift_b->eval_expand(&(double) {xp}, shift_c); -} - -void -ts_interval_dx_and_xc(const double *interval, double *dx, double *xc) -{ - // Compute the lenth (dx) and center (xc) of [interval[0], interval[1]]. - double lo = interval[0], up = interval[1]; - dx[0] = up - lo; - xc[0] = 0.5*(up + lo); -} - -static inline double -ts_grid_length_in_dir(struct gkyl_rect_grid *grid, int dir) -{ - return grid->upper[dir] - grid->lower[dir]; -} - -double -ts_wrap_to_range(double val, double lower, double upper, bool pick_upper) -{ - // Wrap a number to range [lower,upper]. If pickUpper=true, output upper when - // val is a multiple of upper. Otherwise multiples of upper wrap to lower. - double L = upper - lower; - double disp = fmod(val - lower, L); - double vwrapped = lower + fmod(L + disp, L); - double eps = 1.e-12; - if ( (lower-eps < vwrapped && vwrapped < lower + eps) || - (upper-eps < vwrapped && vwrapped < upper + eps) ) { - if (pick_upper) - return upper; - else - return lower; - } - else - return vwrapped; -} - -long -ts_shift_dir_idx_do_linidx(const int *num_do, int shear_dir_idx, int shift_dir_idx, - int shift_dir_num_cells, int shear_r_lower) -{ - // Return the linear index to the first donor for the idx=(i,j) target cell, - // in the shift_dir_idx_do array. We assume shift_dir_idx_do (whose dimensions - // are Nx,Ny,num_do(i)) is in row-major order, and that it has num_do donors - // at each cell in the shear_dir_in_ts_grid direction. - long linc = 0; - // Count the number of donors in cells with an idx in the shear dir lower - // than this one. NOTE: the -1 here is because the idx is often 1-index - // (since ghost cells are the 0th index) but num_do is only defined on the - // local range. - for (int i=0; ishear_dir_in_ts_grid]}; - int shift_idx[] = {idx[up->shift_dir_in_ts_grid]}; - - int *shift_dir_idx_do_buff_ptr = (int *) gkyl_mem_buff_data(shift_dir_idx_do_buff); - - // Evaluate the shift at this test point. - double test_pt_in_shear_dir = test_pt[up->shear_dir_in_ts_grid]; - double xc_in_shear_dir = xc[up->shear_dir_in_ts_grid]; - double dx_in_shear_dir = dx[up->shear_dir_in_ts_grid]; - double shift_at_pt = up->shift_b.eval_expand( - &(double) {ts_p2l(test_pt_in_shear_dir, xc_in_shear_dir, dx_in_shear_dir)}, shift_c); - - // Find the index of the cell that owns the shifted point. - double shifted_test_pt[] = { ts_wrap_to_range(test_pt[up->shift_dir_in_ts_grid] - shift_at_pt, - up->ts_grid.lower[up->shift_dir_in_ts_grid], up->ts_grid.upper[up->shift_dir_in_ts_grid], - false) }; // Shifted test point. - int shift_dir_idx_test_pt[1]; -// gkyl_rect_grid_coord_idx(&up->shift_grid, shifted_test_pt, shift_dir_idx_test_pt); - bool pick_lower_arr[] = {pick_lower}; - gkyl_rect_grid_find_cell(&up->shift_grid, shifted_test_pt, pick_lower_arr, (int[]) {-1}, shift_dir_idx_test_pt); - - // Get the linear index to the list of donors for this target. - long linidx = ts_shift_dir_idx_do_linidx(up->num_do, - shear_idx[0], shift_idx[0], up->ts_grid.cells[up->shift_dir_in_ts_grid], up->shear_r.lower[0]); - // If this donor is not in our list of donors, include it. - bool donor_not_found = true; - for (int k=0; k 0) { - // Insert one more int. Only if num_do_curr>0 because we already - // allocated space for the first donor. - size_t new_buff_sz = gkyl_mem_buff_size(shift_dir_idx_do_buff) + sizeof(int); - shift_dir_idx_do_buff = gkyl_mem_buff_resize(shift_dir_idx_do_buff, new_buff_sz); - } - - // Get the pointer again in case it changed. - shift_dir_idx_do_buff_ptr = (int *) gkyl_mem_buff_data(shift_dir_idx_do_buff); - shift_dir_idx_do_buff_ptr[linidx+num_do_curr[0]] = shift_dir_idx_test_pt[0]; - - num_do_curr[0] += 1; - } -} - -void -ts_find_donors(struct gkyl_bc_twistshift *up) -{ - // Find the donor cells for each target cell in the TS grid. - - double delta_frac = 1.e-9; // Distance away from the boundary, as fraction of cell length. - int num_test_pt[2] = {10, 10}; // Number of test points taken along each side of the cell. - - double step_sz[2] = {0.0}; // Size of the step between test points. - double delta[2] = {0.0}; // Space between cell boundary and test points. - for (int d=0; d<2; d++) { - delta[d] = delta_frac*up->ts_grid.dx[d]; - step_sz[d] = (up->ts_grid.dx[d] - 2.0*delta[d])/(num_test_pt[d]-1); - } - - // Number of donors at each cell of the shear direction. - up->num_do = (int*) gkyl_malloc(up->shear_r.volume * sizeof(int)); - for (int i=0; ishear_r.volume; i++) - up->num_do[i] = -1; - - // Temporary buffer to store donors at (resized below). - size_t curr_buff_sz = up->ts_r.volume * sizeof(int); - gkyl_mem_buff shift_dir_idx_do_buff = gkyl_mem_buff_new(curr_buff_sz); - - int idx[] = {up->shear_r.lower[0]}; - long linidx = gkyl_range_idx(&up->shear_r, idx); - struct gkyl_range_iter iter; - gkyl_range_iter_init(&iter, &up->ts_r); - while (gkyl_range_iter_next(&iter)) { - - // Get the cell boundaries and cell center. - double cell_b[4] = {0.0}; // Cell boundaries, x lo and up, y lo and up; - double xc[2] = {0.0}; // Cell center. - ts_grid_cell_boundaries(&up->ts_grid, iter.idx, cell_b); - gkyl_rect_grid_cell_center(&up->ts_grid, iter.idx, xc); - - int shear_idx[] = {iter.idx[up->shear_dir_in_ts_grid]}; - int shift_idx[] = {iter.idx[up->shift_dir_in_ts_grid]}; - long shift_loc = gkyl_range_idx(&up->shear_r, shear_idx); - double *shift_c = (double *) gkyl_array_fetch(up->shift_dg, shift_loc); - - int num_do_curr = 0; - - for (int dC=0; dC<2; dC++) { // dC=0: x=const, dC=1: y=const (boundaries). - for (int xS=0; xS<2; xS++) { // xS=0: lower, xS=1 upper (boundary). - - double test_pt[2] = {0.0}; // Test point to shift. - for (int d=0; d<2; d++) - test_pt[d] = cell_b[2*d]+delta[d]; - - // Search first shifted point. Use pick_lower=false in find_cell unless - // searching for points along a x=const line near the upper y-boundary. - bool pick_lower = dC==0 && xS==1; - test_pt[dC] += xS*(up->ts_grid.dx[dC]-2.0*delta[dC]); - - // Shift the test point, find the cell that contains it, and if we - // haven't included it yet, add it to our list of donors. - ts_check_shifted_test_point(up, test_pt, xc, up->ts_grid.dx, - shift_c, iter.idx, pick_lower, &num_do_curr, shift_dir_idx_do_buff); - - // Search for other shifted points along this line. - int step_dim = (dC+1) % 2; - for (int sI=1; sIts_grid.dx, - shift_c, iter.idx, pick_lower, &num_do_curr, shift_dir_idx_do_buff); - } - } - } - - up->num_do[shear_idx[0]-up->shear_r.lower[0]] = num_do_curr; - } - - // Copy the donor list to the persistent object and release the buffer. - size_t buff_sz = gkyl_mem_buff_size(shift_dir_idx_do_buff); - up->shift_dir_idx_do = (int *) gkyl_malloc(buff_sz); - int *shift_dir_idx_do_buff_ptr = (int *) gkyl_mem_buff_data(shift_dir_idx_do_buff); - memcpy(up->shift_dir_idx_do, shift_dir_idx_do_buff_ptr, buff_sz); - gkyl_mem_buff_release(shift_dir_idx_do_buff); -} - -struct gkyl_qr_res -ts_root_find(double (*func)(double,void*), void *ctx, const double *lims, int max_iter, double tol) -{ - // Use a Ridder's root finder to find the root of func in the interval - // [lims[0],lims[1]] down to a tolerance 'tol'. Return the interval limit - // if the function is smaller than the tolerance there. Return nil if the - // function does not change sign in the interval (interval doesn't contain the root). - double funcLo = func(lims[0], ctx), funcUp = func(lims[1], ctx); -// if (fabs(funcLo) < tol) -// return (struct gkyl_qr_res) {.res=lims[0], .status=0, .nevals=2}; -// else if (fabs(funcUp) < tol) -// return (struct gkyl_qr_res) {.res=lims[1], .status=0, .nevals=2}; -// else { -// if (funcLo*funcUp < 0) -// return gkyl_ridders(func, ctx, lims[0], lims[1], funcLo, funcUp, max_iter, tol); -// else -// return (struct gkyl_qr_res) {.status=1, .nevals=2}; -// } - if (fabs(funcLo) > tol && fabs(funcUp) > tol) { - if (funcLo*funcUp < 0) - return gkyl_ridders(func, ctx, lims[0], lims[1], funcLo, funcUp, max_iter, tol); - else - return (struct gkyl_qr_res) {.status=1, .nevals=2}; - } - else if (fabs(funcLo) < tol && fabs(funcUp) < tol) - return (struct gkyl_qr_res) {.status=1, .nevals=2}; - else if (fabs(funcLo) < tol) - return (struct gkyl_qr_res) {.res=lims[0], .status=0, .nevals=2}; - else if (fabs(funcUp) < tol) - return (struct gkyl_qr_res) {.res=lims[1], .status=0, .nevals=2}; - return (struct gkyl_qr_res) {.status=1, .nevals=2}; -} - -struct ts_shifted_coord_loss_func_ctx { - double shiftCoordTar; // Target coordinate in shift_dir. - double shiftCoordDo; // Donor coordinate in shift_dir. - double shiftDirL; // Length of the domain in shift_dir. - int periodicCopyIdx; // Used to search a periodic copy of the domain (signed). - evalf_t shift_func; // Function defining the shift. - void *shift_func_ctx; // Context for shift_func. -}; - -double ts_shifted_coord_loss_func(double shearCoord, void *ctx) -{ - // Loss function used to find the shear coord. - struct ts_shifted_coord_loss_func_ctx *tsctx = ctx; - - double shift; - tsctx->shift_func(0.0, (double[]){shearCoord}, &shift, tsctx->shift_func_ctx); - - return tsctx->shiftCoordTar - shift - - (tsctx->shiftCoordDo - tsctx->periodicCopyIdx * tsctx->shiftDirL); -} - -int static inline -ts_sign(double a) -{ - if (a < 0.0) - return -1; - else if (a > 0.0) - return 1; - else - return 0; -} - -double -ts_donor_target_offset(struct gkyl_bc_twistshift *up, const double *xc_do, const double *xc_tar) { - // y-offset between the donor and the target cell (yDo-yTar), in the direction of the shift. - // xc_do: cell center coordinates of donor cell. - // xc_tar: cell center coordinates of target cell. - int shear_dir = up->shear_dir_in_ts_grid; - int shift_dir = up->shift_dir_in_ts_grid; - double x_eval = xc_do[up->shear_dir]; - double shift; - up->shift_func(0.0, (double[]){x_eval}, &shift, up->shift_func_ctx); - - int shift_sign = ts_sign(shift); - double shift_dir_L = up->ts_grid.upper[up->shift_dir] - up->ts_grid.lower[up->shift_dir]; - - // The idea here is that we keep shifting the donor cell center until it is in a - // periodic copy of our domain which overlaps with the shifted target cell center. - double xs_shifted_do = xc_do[shift_dir]; - double xs_shifted_tar = xc_tar[shift_dir] - shift; - bool keep_shifting = true; - while (keep_shifting) { - double xs_shifted_dolo = xs_shifted_do - shift_dir_L/2.0; - double xs_shifted_doup = xs_shifted_do + shift_dir_L/2.0; - if (xs_shifted_dolo <= xs_shifted_tar && xs_shifted_tar <= xs_shifted_doup) { - keep_shifting = false; - break; - } - else - xs_shifted_do = xs_shifted_do - shift_sign*shift_dir_L; - } - return xc_tar[shift_dir] - xs_shifted_do; -} - -struct gkyl_qr_res -ts_find_intersect(struct gkyl_bc_twistshift *up, double shiftCoordTar, double shiftCoordDo, - const double *shearDirBounds, const double *shiftDirLimits) -{ - // Given a y-coordinate of the target cell (yTar), and a y-coordinate - // of the donor cell (yDo), find the x-coordinate of the point where - // the yTar-yShift(x) and y=yDo lines intersect. - // yTar: target cell y coordinate. - // yDo: donor cell y coordinate. - // xBounds: search in the interval [xBounds[1],xBounds[2]]. - // yLims: lower and upper limits of the grid. - // If y-yShift-yDo=0 has no roots, it is possible that y-yShift intersects a periodic - // copy of this domain. Check for such cases by looking for the roots of - // yTar-yShift-(yDo-N*Ly)=0 where Ly is the length of the domain along y and N is an integer. - double tol = 1.e-13; - int max_iter = 100; - - double shiftDirL = shiftDirLimits[1] - shiftDirLimits[0]; - - struct ts_shifted_coord_loss_func_ctx func_ctx = { - .shiftCoordTar = shiftCoordTar, - .shiftCoordDo = shiftCoordDo, - .shiftDirL = shiftDirL, - .periodicCopyIdx = 0, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - struct gkyl_qr_res rootfind_res = ts_root_find(ts_shifted_coord_loss_func, &func_ctx, - shearDirBounds, max_iter, tol); - - if (rootfind_res.status == 1) { - // Maybe yTar-ySh intersects y=yDo in a periodic copy of the domain. Find roots of - // yTar-ySh-(yDo-nP*Ly)=0 where Ly is the y-length of the domain and nP is an integer. - - // Evaluate yTar-ySh at some points in [xBounds.lo,xBounds.up] and obtain potential nP's. - int num_steps = 10; - double step_sz = (shearDirBounds[1]-shearDirBounds[0])/num_steps; - int nP[num_steps+1], num_unique_nP = 0; - for (int sI=0; sIshift_func(0.0, (double[]){xp}, &xp_shift, up->shift_func_ctx); - - double ex_shift = xp_shift<0.? ((shiftCoordTar-xp_shift)-shiftDirLimits[0])/shiftDirL - : (shiftDirLimits[1]-(shiftCoordTar-xp_shift))/shiftDirL; - double nP_new = ts_sign(xp_shift)*floor(fabs(ex_shift)); - // If we haven't accounted for this nP, add it to our list. - bool nP_not_found = true; - for (int n=0; nshift_b.num_basis; ++i) { - ts_comp_to_phys(1, gkyl_eval_on_nodes_fetch_node(up->ev_on_nod1d, i), - dx, xc, xmu); - func(0.0, xmu, (double *)gkyl_array_fetch(up->func_nod1d,i), func_ctx); - } - gkyl_eval_on_nodes_nod2mod(up->ev_on_nod1d, up->func_nod1d, out); -} - -void -ts_integral_xlimdg(struct gkyl_bc_twistshift *up, double sFac, const double *xLimLo, - const double *xLimUp, double yLimLo, double yLimUp, double dyDo, double yOff, - const double *ySh, struct gkyl_mat *mat_do) { - // Populate a matrix (mat_do) with a sub-cell integral that has variably x limits - // represented by a DG polynomial, and a y-integral that goes from yLimLo to yLimUp. - // up: BC updater. - // sFac: +/-1 factor to add or subtract this subcell integral. - // xLimLo: DG representation of the lower x-limit. - // xLimUp: DG representation of the upper x-limit. - // yLimLo: lower y-limit. - // yLimUp: upper y-limit. - // dyDo: Cell length along y. - // yOff: Offset along y. - // ySh: DG representation of the y shift. - // mat_do: donor matrix. - up->kernels->xlimdg(sFac, xLimLo, xLimUp, yLimLo, yLimUp, dyDo, yOff, ySh, mat_do); -} - -void -ts_integral_ylimdg(struct gkyl_bc_twistshift *up, double sFac, double xLimLo, double xLimUp, - const double *yLimLo, const double *yLimUp, double dyDo, double yOff, - const double *ySh, struct gkyl_mat *mat_do) { - // Populate a matrix (mat_do) with a sub-cell integral that has variable y limits - // represented by a DG polynomial, and a x-integral that goes from xLimLo to xLimUp. - // up: BC updater. - // sFac: +/-1 factor to add or subtract this subcell integral. - // xLimLo: lower x-limit. - // xLimUp: upper x-limit. - // yLimLo: DG representation of the lower y-limit. - // yLimUp: DG representation of the upper y-limit. - // dyDo: Cell length along y. - // yOff: Offset along y. - // ySh: DG representation of the y shift. - // mat_do: donor matrix. - up->kernels->ylimdg(sFac, xLimLo, xLimUp, yLimLo, yLimUp, dyDo, yOff, ySh, mat_do); -} - -void -ts_integral_fullcelllimdg(struct gkyl_bc_twistshift *up, double dyDo, double yOff, - const double *ySh, struct gkyl_mat *mat_do) { - // Populate a matrix (mat_do) with the full-cell integral. - // up: BC updater. - // sFac: +/-1 factor to add or subtract this subcell integral. - // dyDo: Cell length along y. - // yOff: Offset along y. - // ySh: DG representation of the y shift. - // mat_do: donor matrix. - up->kernels->fullcell(dyDo, yOff, ySh, mat_do); -} - -static inline void -ts_one(double t, const double *xn, double *fout, void *ctx) -{ - fout[0] = 1.0; -} - -static inline void -ts_minus_one(double t, const double *xn, double *fout, void *ctx) -{ - fout[0] = -1.0; -} - -struct ts_shift_coord_shifted_log_ctx { - double shift_coord_tar; // Target coordinate in shift_dir. - int shift_sign_fac; - const double *xc_do, *xc_tar; // Cell centers (donor and target). - double *dx; // Cell lengths. - bool pick_upper; - int shear_dir, shift_dir; // Shear and shift directions. - double shift_dir_bounds[2]; // Domain boundaries in shift_dir. - evalf_t shift_func; // Function defining the shift. - void *shift_func_ctx; // Context for shift_func. -}; - -void -ts_shift_coord_shifted_log(double t, const double *xn, double *fout, void *ctx) -{ - // Given a logical space x coordinate (xi) and a (physical) y-coordinate in the target cell, - // compute the shifted y-coordinate in the logical space of the donor cell (eta \in [-1,1]). - // xi: logical space x coordinate. - // yTar: physical y-coordinate in target cell. - // pmSh: factor multiplying the y-shift (+/- 1). - // xcDo: cell center coordinates of donor cell. - // xcTar: cell center coordinates of target cell. - // dx: cell lengths. - // pickUpper: boolean indicating if wrapping function should return upper/lower boundary. - - double xi = xn[0]; - - struct ts_shift_coord_shifted_log_ctx *tsctx = ctx; - double shift_coord_tar = tsctx->shift_coord_tar; - int shift_sign_fac = tsctx->shift_sign_fac; - const double *xc_do = tsctx->xc_do, *xc_tar = tsctx->xc_tar; - double *dx = tsctx->dx; - bool pick_upper = tsctx->pick_upper; - int shear_dir = tsctx->shear_dir, shift_dir = tsctx->shift_dir; - double *shift_dir_bounds = tsctx->shift_dir_bounds; - - double shear_coord_phys = xc_tar[shear_dir] + 0.5*dx[shear_dir]*xi; - double shift; - tsctx->shift_func(0.0, (double[]){shear_coord_phys}, &shift, tsctx->shift_func_ctx); - - double shift_coord_shifted = shift_coord_tar - shift_sign_fac * shift; - shift_coord_shifted = ts_wrap_to_range(shift_coord_shifted, shift_dir_bounds[0], shift_dir_bounds[1], pick_upper); - - fout[0] = ts_p2l(shift_coord_shifted, xc_do[shift_dir], dx[shift_dir]); -} - -void -ts_subcellint_sNi_sNii(struct gkyl_bc_twistshift *up, struct ts_val_found *inter_pts, - const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, - bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) -{ - // Perform sNi or sNii subcell integrals. - // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). - // xc_do: cell center of donor cell. - // xc_tar: cell center of target cell. - // cellb_do: boundaries of target cell. - // cellb_tar: boundaries of target cell. - // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? - // shift_c: DG coefficients of the shift. - // mat_do: current donor matrix. - - bool is_sNi = inter_pts[2].value < inter_pts[0].value; - - struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { - .shift_sign_fac = 1, - .xc_do = xc_do, - .xc_tar = xc_tar, - .dx = up->ts_grid.dx, - .pick_upper = is_upper_shift_dir_cell, - .shear_dir = up->shear_dir_in_ts_grid, - .shift_dir = up->shift_dir_in_ts_grid, - .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - - double xi_b[2]; // Limits of xi integral. - evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. - double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; - - // Offset between cell centers in direction of the shift. - double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); - - if (is_sNi) { - // sNi - // 1) Add the contribution of the left portion. - xi_b[0] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - - // 2) Add the contribution of the right portion. - xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - } - else { - // sNii - // 1) Add the contribution of the left portion. - xi_b[0] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - - // 2) Add the contribution of the right portion. - xi_b[0] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - } -} - -void -ts_subcellint_si_sii(struct gkyl_bc_twistshift *up, struct ts_val_found *inter_pts, - const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, - bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) -{ - // Perform subcell integral si or sii, using fixed x-limits and variable y limits. - // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). - // xc_do: cell center of donor cell. - // xc_tar: cell center of target cell. - // cellb_do: boundaries of target cell. - // cellb_tar: boundaries of target cell. - // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? - // shift_c: DG coefficients of the shift. - // mat_do: current donor matrix. - - double x_up = cellb_tar[cellb_up(up->shear_dir_in_ts_grid)]; - double x_lo = cellb_tar[cellb_lo(up->shear_dir_in_ts_grid)]; - double shift_lo, shift_up; - - up->shift_func(0.0, (double[]){x_lo}, &shift_lo, up->shift_func_ctx); - up->shift_func(0.0, (double[]){x_up}, &shift_up, up->shift_func_ctx); - - bool is_si = -shift_lo < -shift_up; - - double xi_b[2]; // Limits of xi integral. - if (is_si) { - // si integral. - xi_b[0] = -1.0; - xi_b[1] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]);; - } - else { - // sii integral. - xi_b[0] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]);; - xi_b[1] = 1.0; - } - - evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - - struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { - .shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)], - .shift_sign_fac = 1, - .xc_do = xc_do, - .xc_tar = xc_tar, - .dx = up->ts_grid.dx, - .pick_upper = is_upper_shift_dir_cell, - .shear_dir = up->shear_dir_in_ts_grid, - .shift_dir = up->shift_dir_in_ts_grid, - .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - - double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - // Offset between cell centers in direction of the shift. - double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); -} - -void -ts_subcellint_siii_siv(struct gkyl_bc_twistshift *up, struct ts_val_found *inter_pts, - const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, - bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) -{ - // Perform subcell integral siii or siv, using fixed x-limits and variable y limits. - // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). - // xc_do: cell center of donor cell. - // xc_tar: cell center of target cell. - // cellb_do: boundaries of target cell. - // cellb_tar: boundaries of target cell. - // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? - // shift_c: DG coefficients of the shift. - // mat_do: current donor matrix. - - double x_lo = cellb_tar[cellb_lo(up->shear_dir_in_ts_grid)]; - double x_up = cellb_tar[cellb_up(up->shear_dir_in_ts_grid)]; - double shift_lo, shift_up; - - up->shift_func(0.0, (double[]){x_lo}, &shift_lo, up->shift_func_ctx); - up->shift_func(0.0, (double[]){x_up}, &shift_up, up->shift_func_ctx); - - bool is_siii = -shift_lo > -shift_up; - - double xi_b[2]; // Limits of xi integral. - if (is_siii) { - // siii integral. - xi_b[0] = -1.0; - xi_b[1] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]);; - } - else { - // siv integral. - xi_b[0] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]);; - xi_b[1] = 1.0; - } - - evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - - struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { - .shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)], - .shift_sign_fac = 1, - .xc_do = xc_do, - .xc_tar = xc_tar, - .dx = up->ts_grid.dx, - .pick_upper = is_upper_shift_dir_cell, - .shear_dir = up->shear_dir_in_ts_grid, - .shift_dir = up->shift_dir_in_ts_grid, - .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - - double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - // Offset between cell centers in direction of the shift. - double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); -} - -void -ts_subcellint_sv_svi(struct gkyl_bc_twistshift *up, struct ts_val_found *inter_pts, - const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, - bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) -{ - // Perform sv or svi subcell integrals. - // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). - // xc_do: cell center of donor cell. - // xc_tar: cell center of target cell. - // cellb_do: boundaries of target cell. - // cellb_tar: boundaries of target cell. - // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? - // shift_c: DG coefficients of the shift. - // mat_do: current donor matrix. - - bool is_sv = inter_pts[3].value < inter_pts[1].value; - - struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { - .shift_sign_fac = 1, - .xc_do = xc_do, - .xc_tar = xc_tar, - .dx = up->ts_grid.dx, - .pick_upper = is_upper_shift_dir_cell, - .shear_dir = up->shear_dir_in_ts_grid, - .shift_dir = up->shift_dir_in_ts_grid, - .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - - double xi_b[2]; // Limits of xi integral. - evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. - double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; - - // Offset between cell centers in direction of the shift. - double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); - - if (is_sv) { - // sv - // 1) Add the contribution of the left portion. - xi_b[0] = -1.0; - xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - - // 2) Add the contribution of the right portion. - xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - } - else { - // svi - // 1) Add the contribution of the left portion. - xi_b[0] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - - // 2) Add the contribution of the right portion. - xi_b[0] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = 1.0; - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - } -} - -void -ts_subcellint_svii_sviii(struct gkyl_bc_twistshift *up, struct ts_val_found *inter_pts, - const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, - bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) -{ - // Perform svii or sviii subcell integrals. - // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). - // xc_do: cell center of donor cell. - // xc_tar: cell center of target cell. - // cellb_do: boundaries of target cell. - // cellb_tar: boundaries of target cell. - // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? - // shift_c: DG coefficients of the shift. - // mat_do: current donor matrix. - - bool is_svii = inter_pts[0].value < inter_pts[2].value; - - struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { - .shift_sign_fac = 1, - .xc_do = xc_do, - .xc_tar = xc_tar, - .dx = up->ts_grid.dx, - .pick_upper = is_upper_shift_dir_cell, - .shear_dir = up->shear_dir_in_ts_grid, - .shift_dir = up->shift_dir_in_ts_grid, - .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - - double xi_b[2]; // Limits of xi integral. - evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. - double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; - - // Offset between cell centers in direction of the shift. - double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); - - if (is_svii) { - // svii - // 1) Add the contribution of the left portion. - xi_b[0] = -1.0; - xi_b[1] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - - // 2) Add the contribution of the right portion. - xi_b[0] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - } - else { - // sviii - // 1) Add the contribution of the left portion. - xi_b[0] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - // 2) Add the contribution of the right portion. - xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = 1.0; +#include - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - } -} - -void -ts_subcellint_six_sx(struct gkyl_bc_twistshift *up, struct ts_val_found *inter_pts, - const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, - bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +static void +bc_twistshift_filter_enabled(struct gkyl_dg_lowpass_filter *filt_up, + struct gkyl_array *GKYL_RESTRICT finout, struct gkyl_array *GKYL_RESTRICT fbuff) { - // Perform six or sx subcell integrals. - // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). - // xc_do: cell center of donor cell. - // xc_tar: cell center of target cell. - // cellb_do: boundaries of target cell. - // cellb_tar: boundaries of target cell. - // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? - // shift_c: DG coefficients of the shift. - // mat_do: current donor matrix. - - bool is_six = inter_pts[0].value < inter_pts[1].value; - - // Limits of xi integral. - double xi_b[2]; - if (is_six) { - // six - xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - } - else { - // sx - xi_b[0] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - } - - struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { - .shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)], - .shift_sign_fac = 1, - .xc_do = xc_do, - .xc_tar = xc_tar, - .dx = up->ts_grid.dx, - .pick_upper = is_upper_shift_dir_cell, - .shear_dir = up->shear_dir_in_ts_grid, - .shift_dir = up->shift_dir_in_ts_grid, - .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - - evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - - double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - // Offset between cell centers in direction of the shift. - double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + gkyl_dg_lowpass_filter_advance(filt_up, finout, fbuff); + gkyl_array_copy(finout, fbuff); } -void -ts_subcellint_sxi_sxii(struct gkyl_bc_twistshift *up, struct ts_val_found *inter_pts, - const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, - bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +static void +bc_twistshift_filter_disabled(struct gkyl_dg_lowpass_filter *filt_up, + struct gkyl_array *GKYL_RESTRICT finout, struct gkyl_array *GKYL_RESTRICT fbuff) { - // Perform sxi or sxii subcell integrals. - // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). - // xc_do: cell center of donor cell. - // xc_tar: cell center of target cell. - // cellb_do: boundaries of target cell. - // cellb_tar: boundaries of target cell. - // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? - // shift_c: DG coefficients of the shift. - // mat_do: current donor matrix. - - bool is_sxi = inter_pts[3].value < inter_pts[2].value; - - // Limits of xi integral. - double xi_b[2]; - if (is_sxi) { - // six - xi_b[0] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - } - else { - // sx - xi_b[0] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - } - - struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { - .shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)], - .shift_sign_fac = 1, - .xc_do = xc_do, - .xc_tar = xc_tar, - .dx = up->ts_grid.dx, - .pick_upper = is_upper_shift_dir_cell, - .shear_dir = up->shear_dir_in_ts_grid, - .shift_dir = up->shift_dir_in_ts_grid, - .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - - evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - - double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - // Offset between cell centers in direction of the shift. - double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + // Do nothing. } -void -ts_subcellint_sxiii_sxiv(struct gkyl_bc_twistshift *up, struct ts_val_found *inter_pts, - const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, - bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) -{ - // Perform sxiii or sxiv subcell integrals. - // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). - // xc_do: cell center of donor cell. - // xc_tar: cell center of target cell. - // cellb_do: boundaries of target cell. - // cellb_tar: boundaries of target cell. - // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? - // shift_c: DG coefficients of the shift. - // mat_do: current donor matrix. - - double x_lo = cellb_tar[cellb_lo(up->shear_dir_in_ts_grid)]; - double x_up = cellb_tar[cellb_up(up->shear_dir_in_ts_grid)]; - double shift_lo, shift_up; - - up->shift_func(0.0, (double[]){x_lo}, &shift_lo, up->shift_func_ctx); - up->shift_func(0.0, (double[]){x_up}, &shift_up, up->shift_func_ctx); - - bool is_sxiii = -shift_lo < -shift_up; - - struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { - .shift_sign_fac = 1, - .xc_do = xc_do, - .xc_tar = xc_tar, - .dx = up->ts_grid.dx, - .pick_upper = is_upper_shift_dir_cell, - .shear_dir = up->shear_dir_in_ts_grid, - .shift_dir = up->shift_dir_in_ts_grid, - .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - - double xi_b[2]; // Limits of xi integral. - evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. - double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; - - // Offset between cell centers in direction of the shift. - double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); - - // 1) Add the contribution of the left portion. - xi_b[0] = -1.0; - xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - - if (is_sxiii) { - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - } - else { - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - } - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); - - // 2) Add the contribution of the right portion. - xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); - xi_b[1] = 1.0; - - if (is_sxiii) { - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - } - else { - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - } - - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); -} - -void -ts_subcellint_sxv_sxvi(struct gkyl_bc_twistshift *up, struct ts_val_found *inter_pts, - const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, - bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +static void +bc_twistshift_refine_enabled(struct gkyl_dg_interpolate *refine, + struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) { - // Perform subcell integral sxv or sxvi, using fixed x-limits and variable y limits. - // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). - // xc_do: cell center of donor cell. - // xc_tar: cell center of target cell. - // cellb_do: boundaries of target cell. - // cellb_tar: boundaries of target cell. - // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? - // shift_c: DG coefficients of the shift. - // mat_do: current donor matrix. - - double xi_b[] = {-1.0, 1.0}; // Limits of xi integral. - - double shift_dir_bounds[] = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}; - - double x_eval = xc_do[up->shear_dir_in_ts_grid]; - double shift; - - up->shift_func(0.0, (double[]){x_eval}, &shift, up->shift_func_ctx); - - double shifted_coord = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)] - shift; - shifted_coord = ts_wrap_to_range(shifted_coord, shift_dir_bounds[0], shift_dir_bounds[1], - is_upper_shift_dir_cell); - - struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { - .shift_sign_fac = 1, - .xc_do = xc_do, - .xc_tar = xc_tar, - .dx = up->ts_grid.dx, - .pick_upper = is_upper_shift_dir_cell, - .shear_dir = up->shear_dir_in_ts_grid, - .shift_dir = up->shift_dir_in_ts_grid, - .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}, - .shift_func = up->shift_func, - .shift_func_ctx = up->shift_func_ctx, - }; - - evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. - if ( cellb_do[cellb_lo(up->shift_dir_in_ts_grid)] <= shifted_coord && - shifted_coord <= cellb_do[cellb_up(up->shift_dir_in_ts_grid)] ) { - // sxv integral. - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_shift_coord_shifted_log; - eta_lims[1] = ts_one; - } - else { - // sxvi integral. - eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; - eta_lims[0] = ts_minus_one; - eta_lims[1] = ts_shift_coord_shifted_log; - } - - double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; - ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); - ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); - // Offset between cell centers in direction of the shift. - double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); - - if (fabs(xi_b[1] - xi_b[0]) > tol_xi) - up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, - up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + gkyl_dg_interpolate_advance(refine, fdo, ftar); } -struct gkyl_nmat * -ts_calc_mats(struct gkyl_bc_twistshift *up) +static void +bc_twistshift_refine_disabled(struct gkyl_dg_interpolate *refine, + struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) { - - // Allocate matrices containing the discrete subcell integrals. - int num_do_tot = 0; - for (int i=0; ishear_r.volume; i++) - num_do_tot += up->num_do[i]; - - struct gkyl_nmat *matsdo = gkyl_nmat_new(num_do_tot, up->basis.num_basis, up->basis.num_basis); - for (int n=0; nnum; ++n) { - struct gkyl_mat mat = gkyl_nmat_get(matsdo, n); - for (int j=0; jnc; ++j) - for (int i=0; inr; ++i) - gkyl_mat_set(&mat, i, j, 0.0); - } - - // y-index of the reference target used to precalc matrices. For positive(negative) - // yShift idx=1(last) might be better, but ideally it shouldn't matter. - int shift_dir_idx_tar = 1; - - double shift_dir_lims[] = {up->ts_grid.lower[up->shift_dir_in_ts_grid], - up->ts_grid.upper[up->shift_dir_in_ts_grid]}; - - // Create an eval_on_nodes updater to use its nodes and functions (but not - // the whole advance method). - up->ev_on_nod1d = gkyl_eval_on_nodes_new(&up->shear_grid, &up->shift_b, 1, ts_one, NULL); - // Create an array to store evaluations of a function at 1D nodes. - up->func_nod1d = gkyl_array_new(GKYL_DOUBLE, 1, up->shift_b.num_basis); - - struct gkyl_range_iter iter; - gkyl_range_iter_init(&iter, &up->shear_r); - while (gkyl_range_iter_next(&iter)) { - - // Get the cell boundaries and cell center. - int idx_tar[2]; // Target index. - idx_tar[up->shift_dir_in_ts_grid] = shift_dir_idx_tar; - idx_tar[up->shear_dir_in_ts_grid] = iter.idx[0]; - double cellb_tar[4] = {0.0}; // Cell boundaries, x lo and up, y lo and up; - double xc_tar[2] = {0.0}; // Cell center. - ts_grid_cell_boundaries(&up->ts_grid, idx_tar, cellb_tar); - gkyl_rect_grid_cell_center(&up->ts_grid, idx_tar, xc_tar); - - long shift_loc = gkyl_range_idx(&up->shear_r, iter.idx); - double *shift_c = (double *) gkyl_array_fetch(up->shift_dg, shift_loc); - - long linidx_do = ts_shift_dir_idx_do_linidx(up->num_do, iter.idx[0], shift_dir_idx_tar, - up->ts_grid.cells[up->shift_dir_in_ts_grid], up->shear_r.lower[0]); - int *shift_dir_idx_do_ptr = &up->shift_dir_idx_do[linidx_do]; - - long linidx_mats_do = 0; - for (int i=0; ishear_r.lower[0]; i++) - linidx_mats_do += up->num_do[i]; - - for (int iC=0; iCnum_do[iter.idx[0]-up->shear_r.lower[0]]; iC++){ - int idx_do[2]; // Target index. - idx_do[up->shift_dir_in_ts_grid] = shift_dir_idx_do_ptr[iC]; - idx_do[up->shear_dir_in_ts_grid] = iter.idx[0]; - - double cellb_do[4] = {0.0}; // Cell boundaries, x lo and up, y lo and up; - double xc_do[2] = {0.0}; // Cell center. - ts_grid_cell_boundaries(&up->ts_grid, idx_do, cellb_do); - gkyl_rect_grid_cell_center(&up->ts_grid, idx_do, xc_do); - - // Get the matrix we are presently assigning. - struct gkyl_mat mat_do = gkyl_nmat_get(matsdo, linidx_mats_do+iC); - - // Find the points where y_{j_tar-/+1/2}-yShift intersect the y=y_{j_do-/+1/2} lines. - // Also record the number and indices of points found/not found. - struct ts_val_found inter_pts[4] = {}; - int num_inter_pts_found = 0, num_inter_pts_not_found = 4; - int inter_pts_found_idxs[4], inter_pts_not_found_idxs[4]; - for (int i=0; i<2; i++) { // Loop over j_tar-/+1/2 - for (int j=0; j<2; j++) { // Loop over j_do-/+1/2 - double shift_dir_coord_tar = cellb_tar[2*up->shift_dir_in_ts_grid+i]; - double shift_dir_coord_do = cellb_do[2*up->shift_dir_in_ts_grid+j]; - struct gkyl_qr_res inter_res = ts_find_intersect(up, shift_dir_coord_tar, shift_dir_coord_do, - (double[]) {cellb_tar[cellb_lo(up->shear_dir_in_ts_grid)],cellb_tar[cellb_up(up->shear_dir_in_ts_grid)]}, - shift_dir_lims); - int ip_linc = i*2+j; - inter_pts[ip_linc].status = inter_res.status == 0; - if (inter_res.status == 0) { - inter_pts[ip_linc].value = inter_res.res; - inter_pts_found_idxs[num_inter_pts_found] = ip_linc; - num_inter_pts_found++; - } - else { - inter_pts_not_found_idxs[num_inter_pts_not_found] = ip_linc; - num_inter_pts_not_found--; - } - } - } - - bool is_upper_shift_dir_cell = idx_do[up->shift_dir_in_ts_grid] == up->ts_grid.cells[up->shift_dir_in_ts_grid]; - - if (num_inter_pts_found == 4) { - // sN: all intersections are found at this cell. - ts_subcellint_sNi_sNii(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); - } - else if (num_inter_pts_found == 1) { - if (inter_pts[1].status) { - // si: y_{j_tar-1/2}-yShift intersects x_{i-1/2}. - // sii: y_{j_tar-1/2}-yShift intersects x_{i+1/2}. - ts_subcellint_si_sii(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); - } - else { - // siii: y_{j_tar+1/2}-yShift intersects x_{i-1/2}. - // siv: y_{j_tar+1/2}-yShift intersects x_{i+1/2}. - ts_subcellint_siii_siv(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); - } - } - else if (num_inter_pts_found == 3) { - if (!inter_pts[2].status) { - // sv: y_{j_tar+1/2}-yShift doesn't intersect y_{j_do-1/2} & intersects x_{i-1/2}. - // svi: y_{j_tar+1/2}-yShift doesn't intersect y_{j_do-1/2} & intersects x_{i+1/2}. - ts_subcellint_sv_svi(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); - } - else { - // svii: y_{j_tar-1/2}-yShift doesn't intersect y_{j_do+1/2} & intersects x_{i-1/2}. - // sviii: y_{j_tar-1/2}-yShift doesn't intersect y_{j_do+1/2} & intersects x_{i+1/2}. - ts_subcellint_svii_sviii(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); - } - } - else if (num_inter_pts_found == 2) { - if (inter_pts[0].status && inter_pts[1].status) { - // six: y_{j_tar-1/2}-yShift crosses y_{j_do-/+1/2} (increasing yShift). - // sx: y_{j_tar-1/2}-yShift crosses y_{j_do-/+1/2} (decreasing yShift). - ts_subcellint_six_sx(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); - } - else if (inter_pts[2].status && inter_pts[3].status) { - // sxi: y_{j_tar+1/2}-yShift crosses y_{j_do-/+1/2} (decreasing yShift). - // sxii: y_{j_tar+1/2}-yShift crosses y_{j_do-/+1/2} (increasing yShift). - ts_subcellint_sxi_sxii(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); - } - else { - // sxiii: y_{j_tar-1/2}-yShift crosses y_{j_do-1/2} & y_{j_tar+1/2}-yShift crosses y_{j_do+1/2} (increasing yShift). - // sxiv: y_{j_tar-1/2}-yShift crosses y_{j_do-1/2} & y_{j_tar+1/2}-yShift crosses y_{j_do+1/2} (decreasing yShift). - ts_subcellint_sxiii_sxiv(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); - } - } - else if (num_inter_pts_found == 0) { - // sxv: y_{j_tar-1/2}-yShift crosses x_{i-/+1/2}. - // sxvi: y_{j_tar+1/2}-yShift crosses x_{i-/+1/2}. - ts_subcellint_sxv_sxvi(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); - } - else { - // An error occurred. This shouldn't happen. - assert(false); - } - } - - } - - gkyl_array_release(up->func_nod1d); - gkyl_eval_on_nodes_release(up->ev_on_nod1d); - - struct gkyl_nmat *matsdo_out = up->use_gpu? gkyl_nmat_cu_dev_new(matsdo->num, matsdo->nr, matsdo->nc) - : gkyl_nmat_acquire(matsdo); - gkyl_nmat_copy(matsdo_out, matsdo); - gkyl_nmat_release(matsdo); - - return matsdo_out; + gkyl_array_copy(ftar, fdo); } -long * -ts_calc_num_numcol_fidx_do(struct gkyl_bc_twistshift *up) -{ - // Calculate the linear indices into the donor distribution function gkyl_array - // for each num-numcol plane (in the num-numcol-num_basis) space. - - long *num_numcol_fidx_do_ho = (long*) gkyl_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); - - // Location in the direction of the BC from which to take donor - // distributions. We assume that the user filled the ghost cell with the skin - // on the other side (i.e. applied periodicity first). - int bc_dir_loc_do = up->edge == GKYL_LOWER_EDGE? up->local_bcdir_ext_r.lower[up->bc_dir] - : up->local_bcdir_ext_r.upper[up->bc_dir]; - - // Range over directions other than shear and bc dirs. - struct gkyl_range shearbc_perp_r; - int remove[GKYL_MAX_DIM] = {0}, loc_in_dir[GKYL_MAX_DIM] = {0};; - remove[up->shear_dir] = remove[up->bc_dir] = 1; - loc_in_dir[up->shear_dir] = up->local_bcdir_ext_r.lower[up->shear_dir]; - loc_in_dir[up->bc_dir] = bc_dir_loc_do; - gkyl_range_deflate(&shearbc_perp_r, &up->local_bcdir_ext_r, remove, loc_in_dir); - - int shift_dir_in_shearbc_perp_r; - if (up->shift_dir < up->shear_dir && up->shift_dir < up->bc_dir) - shift_dir_in_shearbc_perp_r = up->shift_dir; - else if (up->shift_dir > up->shear_dir && up->shift_dir > up->bc_dir) - shift_dir_in_shearbc_perp_r = up->shift_dir-2; - else - shift_dir_in_shearbc_perp_r = up->shift_dir-1; - - int prev_shift_dir_idx = 0; - int donor_count = 0; - int do_idx[up->local_bcdir_ext_r.ndim]; - - // Loop over directions perpendicular to shear and BC dirs. - struct gkyl_range_iter iter; - gkyl_range_iter_init(&iter, &shearbc_perp_r); - while (gkyl_range_iter_next(&iter)) { - int ic = 0; - for (int d=0; dlocal_bcdir_ext_r.ndim; d++) { - if (d != up->bc_dir && d != up->shear_dir) { - do_idx[d] = iter.idx[ic]; - ic++; - } - } - do_idx[up->bc_dir] = bc_dir_loc_do; - - struct gkyl_range_iter shear_dir_iter; - gkyl_range_iter_init(&shear_dir_iter, &up->shear_r); - while (gkyl_range_iter_next(&shear_dir_iter)) { - - int shear_dir_idx = shear_dir_iter.idx[0]; - - long linidx_do = ts_shift_dir_idx_do_linidx(up->num_do, shear_dir_idx, - iter.idx[shift_dir_in_shearbc_perp_r], up->ts_grid.cells[up->shift_dir_in_ts_grid], up->shear_r.lower[0]); - - for (int i = 0; i < up->num_do[shear_dir_idx-up->shear_r.lower[0]]; i++) { - do_idx[up->shear_dir] = shear_dir_idx; - do_idx[up->shift_dir] = up->shift_dir_idx_do[linidx_do+i]; - - long loc = gkyl_range_idx(&up->local_bcdir_ext_r, do_idx); - num_numcol_fidx_do_ho[donor_count] = loc; - - donor_count += 1; - } - } - prev_shift_dir_idx = iter.idx[shift_dir_in_shearbc_perp_r]; - } - - long *num_numcol_fidx_do; - if (!up->use_gpu) { - num_numcol_fidx_do = (long*) gkyl_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); - memcpy(num_numcol_fidx_do, num_numcol_fidx_do_ho, up->fmat->num * up->fmat->nc * sizeof(long)); - } -#ifdef GKYL_HAVE_CUDA - if (up->use_gpu) { - num_numcol_fidx_do = (long*) gkyl_cu_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); - gkyl_cu_memcpy(num_numcol_fidx_do, num_numcol_fidx_do_ho, up->fmat->num * up->fmat->nc * sizeof(long), GKYL_CU_MEMCPY_H2D); - } -#endif - - gkyl_free(num_numcol_fidx_do_ho); - - return num_numcol_fidx_do; -} - -long * -ts_calc_num_numcol_fidx_tar(struct gkyl_bc_twistshift *up) +static void +bc_twistshift_coarsen_enabled(struct gkyl_dg_interpolate *coarsen, + struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) { - - long *num_numcol_fidx_tar_ho = (long*) gkyl_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); - - // Location in the direction of the BC in which to place the target - // distributions. We assume that the local_bcdir_ext_r is a range extended in z (it - // includes the z ghose cell). - int bc_dir_loc_tar = up->edge == GKYL_LOWER_EDGE? up->local_bcdir_ext_r.lower[up->bc_dir] - : up->local_bcdir_ext_r.upper[up->bc_dir]; - - // Range over directions other than shear and bc dirs. - struct gkyl_range shearbc_perp_r; - int remove[GKYL_MAX_DIM] = {0}, loc_in_dir[GKYL_MAX_DIM] = {0};; - remove[up->shear_dir] = remove[up->bc_dir] = 1; - loc_in_dir[up->shear_dir] = up->local_bcdir_ext_r.lower[up->shear_dir]; - loc_in_dir[up->bc_dir] = bc_dir_loc_tar; - gkyl_range_deflate(&shearbc_perp_r, &up->local_bcdir_ext_r, remove, loc_in_dir); - - int tar_idx[up->local_bcdir_ext_r.ndim]; - int tar_count = 0; - - // Loop over directions perpendicular to shear and BC dirs. - struct gkyl_range_iter iter; - gkyl_range_iter_init(&iter, &shearbc_perp_r); - while (gkyl_range_iter_next(&iter)) { - - int ic = 0; - for (int d=0; dlocal_bcdir_ext_r.ndim; d++) { - if (d != up->bc_dir && d != up->shear_dir) { - tar_idx[d] = iter.idx[ic]; - ic++; - } - } - tar_idx[up->bc_dir] = bc_dir_loc_tar; - - - struct gkyl_range_iter shear_dir_iter; - gkyl_range_iter_init(&shear_dir_iter, &up->shear_r); - while (gkyl_range_iter_next(&shear_dir_iter)) { - int shear_dir_idx = shear_dir_iter.idx[0]; - tar_idx[up->shear_dir] = shear_dir_idx; - - long loc = gkyl_range_idx(&up->local_bcdir_ext_r, tar_idx); - num_numcol_fidx_tar_ho[tar_count] = loc; - tar_count += 1; - } - } - - long *num_numcol_fidx_tar; - if (!up->use_gpu) { - num_numcol_fidx_tar = (long*) gkyl_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); - memcpy(num_numcol_fidx_tar, num_numcol_fidx_tar_ho, up->fmat->num * up->fmat->nc * sizeof(long)); - } -#ifdef GKYL_HAVE_CUDA - if (up->use_gpu) { - num_numcol_fidx_tar = (long*) gkyl_cu_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); - gkyl_cu_memcpy(num_numcol_fidx_tar, num_numcol_fidx_tar_ho, up->fmat->num * up->fmat->nc * sizeof(long), GKYL_CU_MEMCPY_H2D); - } -#endif - - gkyl_free(num_numcol_fidx_tar_ho); - - return num_numcol_fidx_tar; + gkyl_dg_interpolate_advance(coarsen, fdo, ftar); } -void -gkyl_bc_twistshift_choose_kernels(struct gkyl_basis basis, int cdim, int shift_poly_order, - struct gkyl_bc_twistshift_kernels *kers) +static void +bc_twistshift_coarsen_disabled(struct gkyl_dg_interpolate *coarsen, + struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) { - int dim = basis.ndim; - int vdim = dim - cdim; - enum gkyl_basis_type basis_type = basis.b_type; - int poly_order = basis.poly_order; - switch (basis_type) { - case GKYL_BASIS_MODAL_GKHYBRID: - case GKYL_BASIS_MODAL_SERENDIPITY: - if (shift_poly_order == 1) { - kers->xlimdg = vdim==0? ser_twistshift_xlimdg_list_0v_yShp1[cdim-2].kernels[poly_order] - : ser_twistshift_xlimdg_list_2v_yShp1[cdim-2].kernels[poly_order]; - kers->ylimdg = vdim==0? ser_twistshift_ylimdg_list_0v_yShp1[cdim-2].kernels[poly_order] - : ser_twistshift_ylimdg_list_2v_yShp1[cdim-2].kernels[poly_order]; - kers->fullcell = vdim==0? ser_twistshift_fullcell_list_0v_yShp1[cdim-2].kernels[poly_order] - : ser_twistshift_fullcell_list_2v_yShp1[cdim-2].kernels[poly_order]; - } - else if (shift_poly_order == 2) { - assert(false); // MF 2025/09/20: removed 3x2v kernel because it's 8.5 MB. - kers->xlimdg = vdim==0? ser_twistshift_xlimdg_list_0v_yShp2[cdim-2].kernels[poly_order] - : ser_twistshift_xlimdg_list_2v_yShp2[cdim-2].kernels[poly_order]; - kers->ylimdg = vdim==0? ser_twistshift_ylimdg_list_0v_yShp2[cdim-2].kernels[poly_order] - : ser_twistshift_ylimdg_list_2v_yShp2[cdim-2].kernels[poly_order]; - kers->fullcell = vdim==0? ser_twistshift_fullcell_list_0v_yShp2[cdim-2].kernels[poly_order] - : ser_twistshift_fullcell_list_2v_yShp2[cdim-2].kernels[poly_order]; - } - return; - default: - assert(false); - break; - } + gkyl_array_copy(ftar, fdo); } struct gkyl_bc_twistshift* gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) { + struct gkyl_bc_twistshift *up = gkyl_malloc(sizeof(*up)); - // Allocate space for new updater. - struct gkyl_bc_twistshift *up = gkyl_malloc(sizeof(struct gkyl_bc_twistshift)); - - up->bc_dir = inp->bc_dir; - up->shift_dir = inp->shift_dir; - up->shear_dir = inp->shear_dir; - up->edge = inp->edge; - up->basis = inp->basis; up->use_gpu = inp->use_gpu; + up->bc_dir = inp->bc_dir; + up->filter_half_width = inp->filter_half_width; + up->filter_cutoff_wavelength = inp->filter_cutoff_wavelength; + up->upsample_factor = inp->upsample_factor > 1 ? inp->upsample_factor : 1; + up->periodic_in_r = inp->periodic_in_r; + up->periodic_out_r = inp->periodic_out_r; const int ndim = inp->bcdir_ext_update_r.ndim; - // Check that it is being used for 3D or 5D. - assert(ndim == 3 || ndim == 5); - - // Supersampling setup. - up->upsample = inp->filter_half_width > 0 && inp->upsample_factor > 1; - if (up->upsample) { - int fine_cells[GKYL_MAX_DIM]; - for (int d=0; dgrid.cells[d]; - fine_cells[up->shift_dir] *= inp->upsample_factor; - fine_cells[up->shear_dir] *= inp->upsample_factor; - gkyl_rect_grid_init(&up->grid, ndim, inp->grid.lower, inp->grid.upper, fine_cells); - - struct gkyl_range fine_ext, fine_local; - gkyl_create_grid_ranges(&up->grid, inp->num_ghost, &fine_ext, &fine_local); - int flo[GKYL_MAX_DIM], fup[GKYL_MAX_DIM]; - for (int d=0; dbc_dir] = fine_ext.lower[up->bc_dir]; - fup[up->bc_dir] = fine_ext.upper[up->bc_dir]; - gkyl_sub_range_init(&up->local_bcdir_ext_r, &fine_ext, flo, fup); - } - else { - up->grid = inp->grid; - up->local_bcdir_ext_r = inp->bcdir_ext_update_r; - } - - // Assume the poly order of the DG shift is the same as that of the field, - // unless requested otherwise. - up->shift_poly_order = inp->basis.poly_order; - if (inp->shift_poly_order) - up->shift_poly_order = inp->shift_poly_order; - - double lo1d[1], up1d[1]; int cells1d[1]; - - // Create 1D grid and range in the direction of the shear. - gkyl_range_init(&up->shear_r, 1, (int[]) {up->local_bcdir_ext_r.lower[inp->shear_dir]}, - (int[]) {up->local_bcdir_ext_r.upper[inp->shear_dir]}); - lo1d[0] = up->grid.lower[up->shear_dir]; - up1d[0] = up->grid.upper[up->shear_dir]; - cells1d[0] = up->grid.cells[up->shear_dir]; - gkyl_rect_grid_init(&up->shear_grid, 1, lo1d, up1d, cells1d); - int idx[] = {up->shear_r.lower[0]}; - long linidx = gkyl_range_idx(&up->shear_r, idx); - - // Create 1D grid and range in the diretion of the shift. - gkyl_range_init(&up->shift_r, 1, (int[]) {up->local_bcdir_ext_r.lower[inp->shift_dir]}, - (int[]) {up->local_bcdir_ext_r.upper[inp->shift_dir]}); - lo1d[0] = up->grid.lower[up->shift_dir]; - up1d[0] = up->grid.upper[up->shift_dir]; - cells1d[0] = up->grid.cells[up->shift_dir]; - gkyl_rect_grid_init(&up->shift_grid, 1, lo1d, up1d, cells1d); - - // Create 2D grid (and range) the twist-shift takes place in. - int dimlo, dimup; - if (up->shift_dir < up->shear_dir) { - dimlo = up->shift_dir; - dimup = up->shear_dir; - up->shift_dir_in_ts_grid = 0; - up->shear_dir_in_ts_grid = 1; - } - else { - dimlo = up->shear_dir; - dimup = up->shift_dir; - up->shift_dir_in_ts_grid = 1; - up->shear_dir_in_ts_grid = 0; - } - gkyl_range_init(&up->ts_r, 2, (int[]) {up->local_bcdir_ext_r.lower[dimlo], up->local_bcdir_ext_r.lower[dimup]}, - (int[]) {up->local_bcdir_ext_r.upper[dimlo], up->local_bcdir_ext_r.upper[dimup]}); - double lo2d[] = {up->grid.lower[dimlo], up->grid.lower[dimup]}; - double up2d[] = {up->grid.upper[dimlo], up->grid.upper[dimup]}; - int cells2d[] = {up->grid.cells[dimlo], up->grid.cells[dimup]}; - gkyl_rect_grid_init(&up->ts_grid, 2, lo2d, up2d, cells2d); - - // Project the shift onto the shift basis. - gkyl_cart_modal_serendip(&up->shift_b, 1, up->shift_poly_order); - if (inp->shift_func) { - up->shift_dg = gkyl_array_new(GKYL_DOUBLE, up->shift_b.num_basis, up->shear_r.volume); - gkyl_eval_on_nodes *evup = gkyl_eval_on_nodes_new(&up->shear_grid, &up->shift_b, 1, - inp->shift_func, inp->shift_func_ctx); - gkyl_eval_on_nodes_advance(evup, 0.0, &up->shear_r, up->shift_dg); - gkyl_eval_on_nodes_release(evup); - } - else { - up->shift_dg = gkyl_array_acquire(inp->shift_dg); - } - - // Function defining the shift (and its context). - if (shift_func_op == 0) { - up->shift_func = ts_shift_dg_eval; - up->shift_dg_eval_ctx.shift_dg = up->shift_dg; - up->shift_dg_eval_ctx.shift_b = &up->shift_b; - up->shift_dg_eval_ctx.shear_grid = &up->shear_grid; - up->shift_dg_eval_ctx.shear_r = &up->shear_r; - up->shift_func_ctx = &up->shift_dg_eval_ctx; - } - else if (shift_func_op == 1) { - up->shift_func = inp->shift_func; - up->shift_func_ctx = inp->shift_func_ctx; - } - else { - fprintf(stderr, "Twist-shift function option not recognized. Exiting...\n"); - assert(false); - } - - // Find the donor cells for each target. Store the number of donors for each - // shear_dir idx (num_do) & the shift_dir idx of each donor (shift_dir_idx_do). - // i.e. allocates and assigns num_do and up->shift_dir_idx_do. - ts_find_donors(up); - - // Array of cummulative number of donors at given shear_dir cell. - const int num_do_cum_sz = up->grid.cells[up->shear_dir]+1; - int num_do_cum_ho[num_do_cum_sz]; - for (int i=0; ishear_r.lower[0]; ishear_r.upper[0]+1; i++) - num_do_cum_ho[i] = num_do_cum_ho[i-1] + up->num_do[i-up->shear_r.lower[0]]; - - if (!up->use_gpu) { - up->num_do_cum = gkyl_malloc(num_do_cum_sz * sizeof(int)); - memcpy(up->num_do_cum, num_do_cum_ho, num_do_cum_sz * sizeof(int)); - } -#ifdef GKYL_HAVE_CUDA - if (up->use_gpu) { - up->num_do_cum = gkyl_cu_malloc(num_do_cum_sz * sizeof(int)); - gkyl_cu_memcpy(up->num_do_cum, num_do_cum_ho, num_do_cum_sz * sizeof(int), GKYL_CU_MEMCPY_H2D); - } -#endif - - // Choose the kernels that do the subcell and full cell integrals - up->kernels = gkyl_malloc(sizeof(struct gkyl_bc_twistshift_kernels)); - gkyl_bc_twistshift_choose_kernels(inp->basis, inp->cdim, up->shift_poly_order, up->kernels); - // The BC is applied as a set of matrix-matrix multiplications - // f_i = sum_{q}^{N_do(i)} A_q,i B_q,i - // where i indicates the shear_dir cell index, A_q is a - // num_basis x num_basis matrix containing the discretization - // of subcell integrals, B_q is a num_basis x (Ny * Nvpar * Nmu) matrix with - // the DG coefficients of f common to a given A_q matrix, and thus where f_i - // is a num_basis x (Ny*Nvpar*Nmu) matrix. - // - // Naming scheme: - // A_q: scimat (subscell integral matrices). - // B_q: fmat (distribution function matrices). - // A_q . B_q: mm_contr (contributions from mat-mat multiplication). - - // Calculate the entries in the matrices used to apply the BC. - up->scimat = ts_calc_mats(up); - - // Number of colums in fmat. - int fmat_num_col = 1; - for (int d=0; dbc_dir && d != up->shear_dir) - fmat_num_col *= up->local_bcdir_ext_r.upper[d] - up->local_bcdir_ext_r.lower[d] + 1; - } - - if (!up->use_gpu) { - up->fmat = gkyl_nmat_new(up->scimat->num, up->scimat->nr, fmat_num_col); - up->mm_contr = gkyl_nmat_new(up->scimat->num, up->scimat->nr, fmat_num_col); - } -#ifdef GKYL_HAVE_CUDA - if (up->use_gpu) { - up->fmat = gkyl_nmat_cu_dev_new(up->scimat->num, up->scimat->nr, fmat_num_col); - up->mm_contr = gkyl_nmat_cu_dev_new(up->scimat->num, up->scimat->nr, fmat_num_col); - } -#endif - - // Index translation from num-numcol plane index to linear index into the - // donor distribution function gkyl_array. - up->num_numcol_fidx_do = ts_calc_num_numcol_fidx_do(up); - - // Index translation from num-numcol plane index to linear index into the - // tar distribution function gkyl_array. - up->num_numcol_fidx_tar = ts_calc_num_numcol_fidx_tar(up); - - // Permutted ghost range, for indexing into the target field. - // Order: Shift direction, redundant directions, shear direction. - int lo4D[ndim-1], up4D[ndim-1]; - lo4D[0] = up->local_bcdir_ext_r.lower[up->shift_dir]; - up4D[0] = up->local_bcdir_ext_r.upper[up->shift_dir]; - int ic = 1; - for (int d=0; dbc_dir && d != up->shear_dir && d != up->shift_dir) { - lo4D[ic] = up->local_bcdir_ext_r.lower[d]; - up4D[ic] = up->local_bcdir_ext_r.upper[d]; - ic++; - } - } - lo4D[ndim-2] = up->local_bcdir_ext_r.lower[up->shear_dir]; - up4D[ndim-2] = up->local_bcdir_ext_r.upper[up->shear_dir]; - gkyl_range_init(&up->permutted_ghost_r, ndim-1, lo4D, up4D); + // Grid and update range for the twist-shift. + int fine_cells[GKYL_MAX_DIM]; + for (int d=0; dgrid.cells[d]; + fine_cells[inp->shear_dir] *= up->upsample_factor; + gkyl_rect_grid_init(&up->ts_grid, ndim, inp->grid.lower, inp->grid.upper, fine_cells); + + struct gkyl_range fine_ext, fine_local; + gkyl_create_grid_ranges(&up->ts_grid, inp->num_ghost, &fine_ext, &fine_local); + int flo[GKYL_MAX_DIM], fup[GKYL_MAX_DIM]; + for (int d=0; dbc_dir] = fine_ext.lower[up->bc_dir]; + fup[up->bc_dir] = fine_ext.upper[up->bc_dir]; + gkyl_sub_range_init(&up->ts_update_r, &fine_ext, flo, fup); + + up->ffine = gkyl_array_new(GKYL_DOUBLE, inp->basis.num_basis, fine_ext.volume); + + // The pure twist-shift updater. + struct gkyl_twistshift_dg_inp tsinp = { + .bc_dir = up->bc_dir, + .shift_dir = inp->shift_dir, + .shear_dir = inp->shear_dir, + .edge = inp->edge, + .cdim = inp->cdim, + .bcdir_ext_update_r = up->ts_update_r, + .num_ghost = inp->num_ghost, + .basis = inp->basis, + .grid = up->ts_grid, + .shift_func = inp->shift_func, + .shift_func_ctx = inp->shift_func_ctx, + .shift_dg = inp->shift_dg, + .use_gpu = inp->use_gpu, + .shift_poly_order = inp->shift_poly_order, + }; + up->ts = gkyl_twistshift_dg_new(&tsinp); - // Create a ghost range, to clear it before adding contributions from TS BC. + // Ghost plane the twist-shift fills, on the ts grid. if (inp->edge == GKYL_LOWER_EDGE) - gkyl_range_shorten_from_above(&up->ghost_r, &up->local_bcdir_ext_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); + gkyl_range_shorten_from_above(&up->ghost_r, &up->ts_update_r, up->bc_dir, inp->num_ghost[up->bc_dir]); else - gkyl_range_shorten_from_below(&up->ghost_r, &up->local_bcdir_ext_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); + gkyl_range_shorten_from_below(&up->ghost_r, &up->ts_update_r, up->bc_dir, inp->num_ghost[up->bc_dir]); - // Optional low-pass filter along shear_dir to de-alias the shifted field. up->filter = NULL; - up->filter_buff = NULL; - if (inp->filter_half_width > 0) - up->filter = gkyl_dg_lowpass_filter_new(up->shear_dir, inp->filter_half_width, - inp->filter_cutoff_wavelength, &up->basis, &up->grid, &up->ghost_r, up->use_gpu); - - // Set up prolongation/coarsening between the coarse and fine grids. - up->prolong = NULL; + up->filt_buff = NULL; + up->filter_func = bc_twistshift_filter_disabled; + up->refine = NULL; up->coarsen = NULL; - up->fine_buff = NULL; - up->coarse_buff = NULL; - if (up->upsample) { - struct gkyl_range coarse_ext, coarse_local, fine_ext, fine_local; - gkyl_create_grid_ranges(&inp->grid, inp->num_ghost, &coarse_ext, &coarse_local); - gkyl_create_grid_ranges(&up->grid, inp->num_ghost, &fine_ext, &fine_local); - - struct gkyl_range opp_ghost, this_ghost_c, this_ghost_f, opp_skin_c; - enum gkyl_edge_loc opp = inp->edge == GKYL_LOWER_EDGE? GKYL_UPPER_EDGE : GKYL_LOWER_EDGE; - gkyl_skin_ghost_ranges(&up->coarse_this_skin, &this_ghost_c, inp->bc_dir, inp->edge, &coarse_ext, inp->num_ghost); - gkyl_skin_ghost_ranges(&up->fine_this_skin, &this_ghost_f, inp->bc_dir, inp->edge, &fine_ext, inp->num_ghost); - gkyl_skin_ghost_ranges(&opp_skin_c, &opp_ghost, inp->bc_dir, opp, &fine_ext, inp->num_ghost); - up->coarse_this_ghost = this_ghost_c; - up->fine_this_ghost = this_ghost_f; - up->fine_opp_skin = opp_skin_c; - - // Interpolate the interior only (periodicity re-derives the fine ghost); - // this also avoids refining coarse ghost cells, which dg_interpolate maps - // out of bounds for refinement factors > 2. - up->prolong = gkyl_dg_interpolate_new(inp->cdim, &up->basis, &inp->grid, &up->grid, - &coarse_local, &fine_local, inp->num_ghost, up->use_gpu); - up->coarsen = gkyl_dg_interpolate_new(inp->cdim, &up->basis, &up->grid, &inp->grid, - &fine_local, &coarse_local, inp->num_ghost, up->use_gpu); - - up->fine_buff = gkyl_array_new(GKYL_DOUBLE, up->basis.num_basis, fine_ext.volume); - up->coarse_buff = gkyl_array_new(GKYL_DOUBLE, up->basis.num_basis, coarse_ext.volume); - } - - return up; -} - -static void -bc_twistshift_advance_core(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar) -{ - // Assign the distribution matrices. - // This assumes that fdo->ncomp = fmat->nr. - // Recall: - // fmat->num = sum_i^Nx num_do(i) - // fmat->nr = num_basis = ncomp - // fmat->nc = Ny*Nvpar*Nmu - for (size_t i=0; ifmat->num * up->fmat->nr * up->fmat->nc; i++) { - - long nc_idx = i / up->fmat->nr; // num-num_col index: current num-num_col plane. - int row_idx = i % up->fmat->nr; // row index: current DG coeff. - - // This if-statement may only be needed in GPU kernel, not for CPUs. - if ((nc_idx < up->fmat->num * up->fmat->nc) && (row_idx < fdo->ncomp)) { - const double *fdo_c = (const double*) gkyl_array_cfetch(fdo, up->num_numcol_fidx_do[nc_idx]); - struct gkyl_mat mcurr = gkyl_nmat_get(up->fmat, nc_idx % up->fmat->num); - - gkyl_mat_set(&mcurr, row_idx, nc_idx/up->fmat->num, fdo_c[row_idx]); - } - } - - // Perform the mat-mat multiplications. - gkyl_nmat_mm(1.0, 0.0, GKYL_NO_TRANS, up->scimat, GKYL_NO_TRANS, up->fmat, up->mm_contr); - - // Clear the ghost range. - gkyl_array_clear_range(ftar, 0.0, &up->ghost_r); - - // Perform reduction over num_do contributions from mat-mat mults (mm_contr). - int num_cells_skin = (up->shear_r.upper[0]-up->shear_r.lower[0]+1) * up->fmat->nc; - for (size_t i=0; incomp * num_cells_skin; i++) { - - long linidx_tar = i / ftar->ncomp; - int row_idx = i % ftar->ncomp; - - // This if-statement may only be needed in GPU kernel, not for CPUs. - if ((linidx_tar < num_cells_skin) && (row_idx < ftar->ncomp)) { - double *ftar_c = (double*) gkyl_array_fetch(ftar, up->num_numcol_fidx_tar[linidx_tar]); - - int idx[GKYL_MAX_DIM] = {1}; - gkyl_sub_range_inv_idx(&up->permutted_ghost_r, linidx_tar, idx); - - int ac[GKYL_MAX_DIM] = {1}; - for (int d=2; dgrid.ndim-1; d++) - ac[d-2] = up->grid.cells[d+1]; - ac[up->permutted_ghost_r.ndim-2] = up->mm_contr->num; - - int start = 0; - for (int d=0; dpermutted_ghost_r.ndim-1; d++) - start = (start + (idx[d]-1)) * ac[d]; - - int shear_idx = idx[up->permutted_ghost_r.ndim-1]; - - int do_start = up->num_do_cum[shear_idx-1]; - int do_end = up->num_do_cum[shear_idx-1+1]; - for (int j=do_start; jmm_contr, linidx_mm_contr % up->mm_contr->num); - ftar_c[row_idx] += gkyl_mat_get(&mat, row_idx, linidx_mm_contr / up->mm_contr->num); - } + up->refine_func = bc_twistshift_refine_disabled; + up->coarsen_func = bc_twistshift_coarsen_disabled; + // Optional low-pass filter + refine/coarsen along shear_dir. + if (up->filter_half_width > 0) { + up->filt_buff = gkyl_array_new(GKYL_DOUBLE, inp->basis.num_basis, up->ffine->size); + up->filter = gkyl_dg_lowpass_filter_new(inp->shear_dir, up->filter_half_width, + up->filter_cutoff_wavelength, &inp->basis, &up->ts_grid, &up->ghost_r, inp->use_gpu); + up->filter_func = bc_twistshift_filter_enabled; + // Refine/coarsen interpolators. + if (up->upsample_factor > 1) { + if (inp->edge == GKYL_LOWER_EDGE) + gkyl_range_shorten_from_above(&up->coarse_ghost_r, &inp->bcdir_ext_update_r, up->bc_dir, inp->num_ghost[up->bc_dir]); + else + gkyl_range_shorten_from_below(&up->coarse_ghost_r, &inp->bcdir_ext_update_r, up->bc_dir, inp->num_ghost[up->bc_dir]); + up->refine = gkyl_dg_interpolate_new(inp->cdim, &inp->basis, &inp->grid, &up->ts_grid, + &up->coarse_ghost_r, &up->ghost_r, inp->num_ghost, inp->use_gpu); + up->coarsen = gkyl_dg_interpolate_new(inp->cdim, &inp->basis, &up->ts_grid, &inp->grid, + &up->ghost_r, &up->coarse_ghost_r, inp->num_ghost, inp->use_gpu); + up->refine_func = bc_twistshift_refine_enabled; + up->coarsen_func = bc_twistshift_coarsen_enabled; } } - // Low-pass filter the shifted ghost field. - if (up->filter) { - if (up->filter_buff == NULL) - up->filter_buff = gkyl_array_new(GKYL_DOUBLE, ftar->ncomp, ftar->size); - gkyl_array_copy_range(up->filter_buff, ftar, &up->ghost_r); - gkyl_dg_lowpass_filter_advance(up->filter, up->filter_buff, ftar); - } + return up; } void gkyl_bc_twistshift_advance(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar) { -#ifdef GKYL_HAVE_CUDA - if (up->use_gpu) { - gkyl_bc_twistshift_advance_cu(up, fdo, ftar); - return; - } -#endif - - if (up->upsample) { - // Prolong to the fine grid, apply periodicity, shift+filter, coarsen, and - // copy the coarsened skin into ftar's ghost. - gkyl_dg_interpolate_advance(up->prolong, fdo, up->fine_buff); - gkyl_array_copy_range_to_range(up->fine_buff, up->fine_buff, &up->fine_this_ghost, &up->fine_opp_skin); - bc_twistshift_advance_core(up, up->fine_buff, up->fine_buff); - gkyl_array_copy_range_to_range(up->fine_buff, up->fine_buff, &up->fine_this_skin, &up->fine_this_ghost); - gkyl_dg_interpolate_advance(up->coarsen, up->fine_buff, up->coarse_buff); - gkyl_array_copy_range_to_range(ftar, up->coarse_buff, &up->coarse_this_ghost, &up->coarse_this_skin); - } - else { - bc_twistshift_advance_core(up, fdo, ftar); - } + // Apply periodicity. + gkyl_array_copy_range_to_range(fdo, fdo, up->periodic_in_r, up->periodic_out_r); + // Refine the data to the fine grid. + up->refine_func(up->refine, fdo, up->ffine); + // Apply twist-shift. + gkyl_twistshift_dg_advance(up->ts, up->ffine, up->ffine); + // Apply the low-pass filter. + up->filter_func(up->filter, up->ffine, up->filt_buff); + // Move back the data to the original grid. + up->coarsen_func(up->coarsen, up->ffine, ftar); } -struct gkyl_array* -gkyl_bc_twistshift_get_shift_objects(struct gkyl_bc_twistshift *up, struct gkyl_rect_grid *shear_grid, - struct gkyl_range *shear_r, struct gkyl_basis *shift_b) -{ - *shear_grid = up->shear_grid; - *shear_r = up->shear_r ; - *shift_b = up->shift_b ; - return gkyl_array_acquire(up->shift_dg); -}; - void -gkyl_bc_twistshift_release(struct gkyl_bc_twistshift *up) { - // Release memory associated with this updater. - if (!up->use_gpu) { - gkyl_free(up->num_do_cum); - gkyl_free(up->num_numcol_fidx_do); - gkyl_free(up->num_numcol_fidx_tar); - } -#ifdef GKYL_HAVE_CUDA - if (up->use_gpu) { - gkyl_cu_free(up->num_do_cum); - gkyl_cu_free(up->num_numcol_fidx_do); - gkyl_cu_free(up->num_numcol_fidx_tar); - } -#endif - - gkyl_nmat_release(up->fmat); - gkyl_nmat_release(up->mm_contr); - - gkyl_nmat_release(up->scimat); - - gkyl_free(up->kernels); - - gkyl_array_release(up->shift_dg); - - if (up->filter) { +gkyl_bc_twistshift_release(struct gkyl_bc_twistshift *up) +{ + gkyl_twistshift_dg_release(up->ts); + gkyl_array_release(up->ffine); + if (up->filter_half_width > 0) { + gkyl_array_release(up->filt_buff); gkyl_dg_lowpass_filter_release(up->filter); - if (up->filter_buff) - gkyl_array_release(up->filter_buff); - } - - if (up->upsample) { - gkyl_dg_interpolate_release(up->prolong); - gkyl_dg_interpolate_release(up->coarsen); - gkyl_array_release(up->fine_buff); - gkyl_array_release(up->coarse_buff); + if (up->upsample_factor > 1) { + gkyl_dg_interpolate_release(up->refine); + gkyl_dg_interpolate_release(up->coarsen); + } } - - gkyl_free(up->num_do); - gkyl_free(up->shift_dir_idx_do); - gkyl_free(up); } diff --git a/gyrokinetic/zero/gkyl_bc_twistshift.h b/gyrokinetic/zero/gkyl_bc_twistshift.h index c2a39c726a..72d485c19b 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift.h @@ -7,14 +7,16 @@ #include #include -// Object type +// Object type. typedef struct gkyl_bc_twistshift gkyl_bc_twistshift; struct gkyl_bc_twistshift_inp { int bc_dir; // Direction in which to apply this BC. + struct gkyl_range *periodic_out_r; // Range of the periodic donor (output) field. + struct gkyl_range *periodic_in_r; // Range of the periodic donor (input) field. int shift_dir; // Direction of the shift. int shear_dir; // Direction in which the shift varies (shear). - enum gkyl_edge_loc edge; // Edge of to apply this BC at (lower/upper). + enum gkyl_edge_loc edge; // Edge to apply this BC at (lower/upper). int cdim; // Configuration space dimensions. struct gkyl_range bcdir_ext_update_r; // Local range where to apply BC, extended in bc_dir. const int *num_ghost; // Number of ghost cells in each direction. @@ -26,22 +28,28 @@ struct gkyl_bc_twistshift_inp { bool use_gpu; // Whether to apply the BC using the GPU. // Optional inputs: int shift_poly_order; // Basis order for the DG representation of the shift. - int filter_half_width; // Filter stencil half-width M in cells. - double filter_cutoff_wavelength; // Filter cutoff wavelength. - int upsample_factor; // Supersampling factor. + // Optional anti-aliasing: a low-pass filter along shear_dir (enabled when + // filter_half_width > 0), applied on a grid supersampled by upsample_factor + // along shear_dir (used only when the filter is on). Both act on the shifted + // ghost plane only. + int filter_half_width; // Filter stencil half-width M in cells (0 = no filter). + double filter_cutoff_wavelength; // Filter cutoff wavelength (physical units). + int upsample_factor; // Supersampling factor along shear_dir (0/1 = none). }; /** - * Create a new updater to apply twist-shift BCs. + * Create a new updater to apply the twist-shift BC, optionally with + * supersampling and low-pass filtering to de-alias the shifted field. It + * combines a gkyl_twistshift_dg updater with, when requested, a + * gkyl_dg_lowpass_filter and gkyl_dg_interpolate operators. * * @param inp bc_twistshift_inp struct containing the inputs to the updater. * @return New updater pointer. */ struct gkyl_bc_twistshift* gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp); - + /** - * Apply the twist-shift. It assumes that periodicity along bc_dir has been - * applied to the donor field. Can be used in-place. + * Apply the twist-shift periodic BC. Can be used in-place. * * @param up Twist-shift BC updater object. * @param fdo Donor field. @@ -50,21 +58,7 @@ struct gkyl_bc_twistshift* gkyl_bc_twistshift_new(const struct gkyl_bc_twistshif void gkyl_bc_twistshift_advance(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar); /** - * Return pointers to the discretized shift, its range, and grid and basis. - * - * Pointer to shift_dg needs to be released with gkyl_array_release. - * - * @param up Twist-shift BC updater object. - * @param shear_grid Grid on which shift is defined. - * @param shear_r Range for the shift. - * @param shift_b Basis shift_dg coefficients are expanded on. - * @return Discretized shift. - */ -struct gkyl_array* gkyl_bc_twistshift_get_shift_objects(struct gkyl_bc_twistshift *up, - struct gkyl_rect_grid *shear_grid, struct gkyl_range *shear_r, struct gkyl_basis *shift_b); - -/** - * Free memory associated with bc_twistshift updater. + * Free memory associated with the bc_twistshift updater. * * @param up BC updater. */ diff --git a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h index a0a6b68147..3dd134a9db 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h @@ -1,179 +1,38 @@ #pragma once -// Private header for bc_twistshift updater, not for direct use in user code. +// Private header for the bc_twistshift orchestrator, not for direct use in +// user code. #include -#include +#include #include #include -#include -#include -#include -#include -#include // memcpy - -// Function pointer type for twistshift kernels. -typedef void (*twistshift_xlimdg_t)(double sFac, const double *xLimLo, - const double *xLimUp, double yLimLo, double yLimUp, - double dyDo, double yOff, const double *ySh, struct gkyl_mat *tsmat); - -typedef void (*twistshift_ylimdg_t)(double sFac, double xLimLo, - double xLimUp, const double *yLimLo, const double *yLimUp, - double dyDo, double yOff, const double *ySh, struct gkyl_mat *tsmat); - -typedef void (*twistshift_fullcell_t)(double dyDo, double yOff, - const double *ySh, struct gkyl_mat *tsmat); - -typedef struct { twistshift_xlimdg_t kernels[3]; } twistshift_xlimdg_kern_list; // For use in kernel tables. -typedef struct { twistshift_ylimdg_t kernels[3]; } twistshift_ylimdg_kern_list; // For use in kernel tables. -typedef struct { twistshift_fullcell_t kernels[3]; } twistshift_fullcell_kern_list; // For use in kernel tables. - -// Serendipity kernels. -// p=1 representation of the shift: -static const twistshift_xlimdg_kern_list ser_twistshift_xlimdg_list_0v_yShp1[] = { - {NULL, twistshift_xlimdg_2x_ser_p1_yshift_p1, NULL,}, - {NULL, twistshift_xlimdg_3x_ser_p1_yshift_p1, NULL,}, -}; -static const twistshift_ylimdg_kern_list ser_twistshift_ylimdg_list_0v_yShp1[] = { - {NULL, twistshift_ylimdg_2x_ser_p1_yshift_p1, NULL,}, - {NULL, twistshift_ylimdg_3x_ser_p1_yshift_p1, NULL,}, -}; -static const twistshift_fullcell_kern_list ser_twistshift_fullcell_list_0v_yShp1[] = { - {NULL, twistshift_fullcell_2x_ser_p1_yshift_p1, NULL,}, - {NULL, twistshift_fullcell_3x_ser_p1_yshift_p1, NULL,}, -}; - -static const twistshift_xlimdg_kern_list ser_twistshift_xlimdg_list_2v_yShp1[] = { - {NULL, NULL, NULL,}, - {NULL, twistshift_xlimdg_3x2v_ser_p1_yshift_p1, NULL,}, -}; -static const twistshift_ylimdg_kern_list ser_twistshift_ylimdg_list_2v_yShp1[] = { - {NULL, NULL, NULL,}, - {NULL, twistshift_ylimdg_3x2v_ser_p1_yshift_p1, NULL,}, -}; -static const twistshift_fullcell_kern_list ser_twistshift_fullcell_list_2v_yShp1[] = { - {NULL, NULL, NULL,}, - {NULL, twistshift_fullcell_3x2v_ser_p1_yshift_p1, NULL,}, -}; -// p=2 representation of the shift: -static const twistshift_xlimdg_kern_list ser_twistshift_xlimdg_list_0v_yShp2[] = { - {NULL, twistshift_xlimdg_2x_ser_p1_yshift_p2, NULL,}, - {NULL, twistshift_xlimdg_3x_ser_p1_yshift_p2, NULL,}, -}; -static const twistshift_ylimdg_kern_list ser_twistshift_ylimdg_list_0v_yShp2[] = { - {NULL, twistshift_ylimdg_2x_ser_p1_yshift_p2, NULL,}, - {NULL, twistshift_ylimdg_3x_ser_p1_yshift_p2, NULL,}, -}; -static const twistshift_fullcell_kern_list ser_twistshift_fullcell_list_0v_yShp2[] = { - {NULL, twistshift_fullcell_2x_ser_p1_yshift_p2, NULL,}, - {NULL, twistshift_fullcell_3x_ser_p1_yshift_p2, NULL,}, -}; - -static const twistshift_xlimdg_kern_list ser_twistshift_xlimdg_list_2v_yShp2[] = { - {NULL, NULL, NULL,}, - {NULL, NULL, NULL,}, -}; -static const twistshift_ylimdg_kern_list ser_twistshift_ylimdg_list_2v_yShp2[] = { - {NULL, NULL, NULL,}, - {NULL, NULL, NULL,}, -}; -static const twistshift_fullcell_kern_list ser_twistshift_fullcell_list_2v_yShp2[] = { - {NULL, NULL, NULL,}, - {NULL, NULL, NULL,}, -}; - - -struct gkyl_bc_twistshift_kernels { - twistshift_xlimdg_t xlimdg; - twistshift_ylimdg_t ylimdg; - twistshift_fullcell_t fullcell; -}; - -struct ts_shift_dg_eval_ctx { - struct gkyl_array *shift_dg; // DG representation of the shift function. - struct gkyl_basis *shift_b; // Basis for the shift. - struct gkyl_rect_grid *shear_grid; // shear grid along x. - struct gkyl_range *shear_r; // Shear grid range. -}; +#include +#include // Primary struct in this updater. struct gkyl_bc_twistshift { - int bc_dir; // Direction of the BC is applied in. - int shift_dir; // Direction of the shift. - int shear_dir; // Direction the shift varies in (shear). - enum gkyl_edge_loc edge; // Indicates if BC is for lowe/upper edge. - struct gkyl_basis basis; // Basis the shifted field is defined with. - struct gkyl_range local_bcdir_ext_r; // Local range. - struct gkyl_rect_grid grid; // Grid the shifted field is defined in. - evalf_t shift_func; // Function defining the shift. - void *shift_func_ctx; // Context for shift_func. - struct ts_shift_dg_eval_ctx shift_dg_eval_ctx; // Context for DG shift_func. bool use_gpu; // Whether to apply the BC on the GPU. - struct gkyl_rect_grid shift_grid; // 1D grid in the direction of the shift. - struct gkyl_range shift_r; // 1D range in the direction of the shift. - - struct gkyl_rect_grid shear_grid; // 1D grid in the direction of the shear. - struct gkyl_range shear_r; // 1D range in the direction of the shear. - - struct gkyl_rect_grid ts_grid; // Grid the shift twistshift takes place in. - struct gkyl_range ts_r; // Range the twistshift takes place in. - int shift_dir_in_ts_grid; // Dimension the shift is in, in the TS grid. - int shear_dir_in_ts_grid; // Dimension the shear is in, in the TS grid. - - int shift_poly_order; // Poly order of the DG representation of the shift. - struct gkyl_basis shift_b; // 1D Basis for the DG shift. - struct gkyl_array *shift_dg; // DG shift. - - int *num_do; // Number of donors at each cell in shear_dir; - int *shift_dir_idx_do; // Indices of donor cells, in the direction of the - // shift, for each cell in the TS grid. - - struct gkyl_bc_twistshift_kernels *kernels; // kernels for sub-cell integrals. - - // Projection object used in constructing the matrices. - struct gkyl_eval_on_nodes *ev_on_nod1d; - // Evaluations of a function at 1D nodes. - struct gkyl_array *func_nod1d; - - struct gkyl_nmat *scimat; // Subcell integral matrices. - struct gkyl_nmat *fmat; // Distribution function matrices. - struct gkyl_nmat *mm_contr; // Contribution resulting from a mat-mat mult. - - long *num_numcol_fidx_do; // 1D indexer, from a index identitying the num-numcol - // plane (in the num-numcol-num_basis space), to a - // linear index into the donor distribution function f. - - long *num_numcol_fidx_tar; // 1D indexer, from a index identitying the num-numcol - // plane (in the num-numcol-num_basis space), to a - // linear index into the target distribution function f. - - int *num_do_cum; // Cumulative number of donors up to a give cell in shear_dir; - struct gkyl_range permutted_ghost_r; // Ghost range to populate in the target - // field, with some dimensions permutted. - struct gkyl_range ghost_r; // Ghost range this BC fills. - - struct gkyl_dg_lowpass_filter *filter; // Post-shift filter. - struct gkyl_array *filter_buff; // Scratch buffer for the filter. - - bool upsample; // Whether to upsample before the shift. - struct gkyl_dg_interpolate *prolong; // Coarse to fine operator. - struct gkyl_dg_interpolate *coarsen; // Fine to coarse operator. - struct gkyl_array *fine_buff; // Field on the fine grid. - struct gkyl_array *coarse_buff; // Field on the coarse grid. - struct gkyl_range fine_this_skin, fine_this_ghost; // Fine skin/ghost at this edge. - struct gkyl_range fine_opp_skin; // Fine skin at the opposite edge (periodicity donor). - struct gkyl_range coarse_this_skin, coarse_this_ghost; // Coarse skin/ghost at this edge. + struct gkyl_twistshift_dg *ts; // Pure twist-shift updater. + int bc_dir; // Direction along which we treat the BC. + struct gkyl_range *periodic_out_r; // Range of the periodic donor (output) field. + struct gkyl_range *periodic_in_r; // Range of the periodic donor (input) field. + + struct gkyl_dg_lowpass_filter *filter; // Optional post-shift filter along shear_dir. + int filter_half_width; // Filter stencil half-width M in cells (0 = no filter). + int filter_cutoff_wavelength; // Filter cutoff wavelength. + struct gkyl_range ghost_r; // Ghost plane the twist-shift fills. + struct gkyl_array *filt_buff; // Buffer for the filter. + void (*filter_func)(struct gkyl_dg_lowpass_filter *filt_up, struct gkyl_array *GKYL_RESTRICT finout, struct gkyl_array *GKYL_RESTRICT fbuff); + + struct gkyl_rect_grid ts_grid; // Grid refined along shear_dir. + struct gkyl_range ts_update_r; // Update range on the ts_grid. + int upsample_factor; // Supersampling factor along shear_dir. + struct gkyl_dg_interpolate *refine; // Coarse ghost plane -> fine ghost plane. + struct gkyl_dg_interpolate *coarsen; // Fine ghost plane -> coarse ghost plane. + struct gkyl_array *ffine; // Field on the fine grid. + struct gkyl_range coarse_ghost_r; // Ghost plane on the coarse grid (donor and target). + void (*refine_func)(struct gkyl_dg_interpolate *refine, struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar); + void (*coarsen_func)(struct gkyl_dg_interpolate *coarsen, struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar); }; - -#ifdef GKYL_HAVE_CUDA -/** - * Apply the twist-shift on the NVIDIA GPU. It assumes that periodicity along bc_dir has been - * applied to the donor field. Can be used in-place. - * - * @param up Twist-shift BC updater object. - * @param fdo Donor field. - * @param ftar Target field. - */ -void gkyl_bc_twistshift_advance_cu(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar); -#endif diff --git a/gyrokinetic/zero/gkyl_twistshift_dg.h b/gyrokinetic/zero/gkyl_twistshift_dg.h new file mode 100644 index 0000000000..13028f5324 --- /dev/null +++ b/gyrokinetic/zero/gkyl_twistshift_dg.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +// Object type +typedef struct gkyl_twistshift_dg gkyl_twistshift_dg; + +struct gkyl_twistshift_dg_inp { + int bc_dir; // Direction in which to apply this BC. + int shift_dir; // Direction of the shift. + int shear_dir; // Direction in which the shift varies (shear). + enum gkyl_edge_loc edge; // Edge of to apply this BC at (lower/upper). + int cdim; // Configuration space dimensions. + struct gkyl_range bcdir_ext_update_r; // Local range where to apply BC, extended in bc_dir. + const int *num_ghost; // Number of ghost cells in each direction. + struct gkyl_basis basis; // Basis of the field shifted. + struct gkyl_rect_grid grid; // Grid the field shifted is defined on. + evalf_t shift_func; // Function defining the shift. + void *shift_func_ctx; // Context for shift_func. + struct gkyl_array *shift_dg; // Discretized shift. + bool use_gpu; // Whether to apply the BC using the GPU. + // Optional inputs: + int shift_poly_order; // Basis order for the DG representation of the shift. +}; + +/** + * Create a new updater to apply twist-shift BCs. + * + * @param inp twistshift_dg_inp struct containing the inputs to the updater. + * @return New updater pointer. + */ +struct gkyl_twistshift_dg* gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp); + +/** + * Apply the twist-shift. It assumes that periodicity along bc_dir has been + * applied to the donor field. Can be used in-place. + * + * @param up Twist-shift BC updater object. + * @param fdo Donor field. + * @param ftar Target field. + */ +void gkyl_twistshift_dg_advance(struct gkyl_twistshift_dg *up, struct gkyl_array *fdo, struct gkyl_array *ftar); + +/** + * Return pointers to the discretized shift, its range, and grid and basis. + * + * Pointer to shift_dg needs to be released with gkyl_array_release. + * + * @param up Twist-shift BC updater object. + * @param shear_grid Grid on which shift is defined. + * @param shear_r Range for the shift. + * @param shift_b Basis shift_dg coefficients are expanded on. + * @return Discretized shift. + */ +struct gkyl_array* gkyl_twistshift_dg_get_shift_objects(struct gkyl_twistshift_dg *up, + struct gkyl_rect_grid *shear_grid, struct gkyl_range *shear_r, struct gkyl_basis *shift_b); + +/** + * Free memory associated with twistshift_dg updater. + * + * @param up BC updater. + */ +void gkyl_twistshift_dg_release(struct gkyl_twistshift_dg *up); diff --git a/gyrokinetic/zero/gkyl_twistshift_dg_priv.h b/gyrokinetic/zero/gkyl_twistshift_dg_priv.h new file mode 100644 index 0000000000..d927f0edea --- /dev/null +++ b/gyrokinetic/zero/gkyl_twistshift_dg_priv.h @@ -0,0 +1,165 @@ +#pragma once + +// Private header for twistshift_dg updater, not for direct use in user code. + +#include +#include +#include +#include +#include +#include +#include // memcpy + +// Function pointer type for twistshift kernels. +typedef void (*twistshift_xlimdg_t)(double sFac, const double *xLimLo, + const double *xLimUp, double yLimLo, double yLimUp, + double dyDo, double yOff, const double *ySh, struct gkyl_mat *tsmat); + +typedef void (*twistshift_ylimdg_t)(double sFac, double xLimLo, + double xLimUp, const double *yLimLo, const double *yLimUp, + double dyDo, double yOff, const double *ySh, struct gkyl_mat *tsmat); + +typedef void (*twistshift_fullcell_t)(double dyDo, double yOff, + const double *ySh, struct gkyl_mat *tsmat); + +typedef struct { twistshift_xlimdg_t kernels[3]; } twistshift_xlimdg_kern_list; // For use in kernel tables. +typedef struct { twistshift_ylimdg_t kernels[3]; } twistshift_ylimdg_kern_list; // For use in kernel tables. +typedef struct { twistshift_fullcell_t kernels[3]; } twistshift_fullcell_kern_list; // For use in kernel tables. + +// Serendipity kernels. +// p=1 representation of the shift: +static const twistshift_xlimdg_kern_list ser_twistshift_xlimdg_list_0v_yShp1[] = { + {NULL, twistshift_xlimdg_2x_ser_p1_yshift_p1, NULL,}, + {NULL, twistshift_xlimdg_3x_ser_p1_yshift_p1, NULL,}, +}; +static const twistshift_ylimdg_kern_list ser_twistshift_ylimdg_list_0v_yShp1[] = { + {NULL, twistshift_ylimdg_2x_ser_p1_yshift_p1, NULL,}, + {NULL, twistshift_ylimdg_3x_ser_p1_yshift_p1, NULL,}, +}; +static const twistshift_fullcell_kern_list ser_twistshift_fullcell_list_0v_yShp1[] = { + {NULL, twistshift_fullcell_2x_ser_p1_yshift_p1, NULL,}, + {NULL, twistshift_fullcell_3x_ser_p1_yshift_p1, NULL,}, +}; + +static const twistshift_xlimdg_kern_list ser_twistshift_xlimdg_list_2v_yShp1[] = { + {NULL, NULL, NULL,}, + {NULL, twistshift_xlimdg_3x2v_ser_p1_yshift_p1, NULL,}, +}; +static const twistshift_ylimdg_kern_list ser_twistshift_ylimdg_list_2v_yShp1[] = { + {NULL, NULL, NULL,}, + {NULL, twistshift_ylimdg_3x2v_ser_p1_yshift_p1, NULL,}, +}; +static const twistshift_fullcell_kern_list ser_twistshift_fullcell_list_2v_yShp1[] = { + {NULL, NULL, NULL,}, + {NULL, twistshift_fullcell_3x2v_ser_p1_yshift_p1, NULL,}, +}; +// p=2 representation of the shift: +static const twistshift_xlimdg_kern_list ser_twistshift_xlimdg_list_0v_yShp2[] = { + {NULL, twistshift_xlimdg_2x_ser_p1_yshift_p2, NULL,}, + {NULL, twistshift_xlimdg_3x_ser_p1_yshift_p2, NULL,}, +}; +static const twistshift_ylimdg_kern_list ser_twistshift_ylimdg_list_0v_yShp2[] = { + {NULL, twistshift_ylimdg_2x_ser_p1_yshift_p2, NULL,}, + {NULL, twistshift_ylimdg_3x_ser_p1_yshift_p2, NULL,}, +}; +static const twistshift_fullcell_kern_list ser_twistshift_fullcell_list_0v_yShp2[] = { + {NULL, twistshift_fullcell_2x_ser_p1_yshift_p2, NULL,}, + {NULL, twistshift_fullcell_3x_ser_p1_yshift_p2, NULL,}, +}; + +static const twistshift_xlimdg_kern_list ser_twistshift_xlimdg_list_2v_yShp2[] = { + {NULL, NULL, NULL,}, + {NULL, NULL, NULL,}, +}; +static const twistshift_ylimdg_kern_list ser_twistshift_ylimdg_list_2v_yShp2[] = { + {NULL, NULL, NULL,}, + {NULL, NULL, NULL,}, +}; +static const twistshift_fullcell_kern_list ser_twistshift_fullcell_list_2v_yShp2[] = { + {NULL, NULL, NULL,}, + {NULL, NULL, NULL,}, +}; + + +struct gkyl_twistshift_dg_kernels { + twistshift_xlimdg_t xlimdg; + twistshift_ylimdg_t ylimdg; + twistshift_fullcell_t fullcell; +}; + +struct ts_shift_dg_eval_ctx { + struct gkyl_array *shift_dg; // DG representation of the shift function. + struct gkyl_basis *shift_b; // Basis for the shift. + struct gkyl_rect_grid *shear_grid; // shear grid along x. + struct gkyl_range *shear_r; // Shear grid range. +}; + +// Primary struct in this updater. +struct gkyl_twistshift_dg { + int bc_dir; // Direction of the BC is applied in. + int shift_dir; // Direction of the shift. + int shear_dir; // Direction the shift varies in (shear). + enum gkyl_edge_loc edge; // Indicates if BC is for lowe/upper edge. + struct gkyl_basis basis; // Basis the shifted field is defined with. + struct gkyl_range local_bcdir_ext_r; // Local range. + struct gkyl_rect_grid grid; // Grid the shifted field is defined in. + evalf_t shift_func; // Function defining the shift. + void *shift_func_ctx; // Context for shift_func. + struct ts_shift_dg_eval_ctx shift_dg_eval_ctx; // Context for DG shift_func. + bool use_gpu; // Whether to apply the BC on the GPU. + + struct gkyl_rect_grid shift_grid; // 1D grid in the direction of the shift. + struct gkyl_range shift_r; // 1D range in the direction of the shift. + + struct gkyl_rect_grid shear_grid; // 1D grid in the direction of the shear. + struct gkyl_range shear_r; // 1D range in the direction of the shear. + + struct gkyl_rect_grid ts_grid; // Grid the shift twistshift takes place in. + struct gkyl_range ts_r; // Range the twistshift takes place in. + int shift_dir_in_ts_grid; // Dimension the shift is in, in the TS grid. + int shear_dir_in_ts_grid; // Dimension the shear is in, in the TS grid. + + int shift_poly_order; // Poly order of the DG representation of the shift. + struct gkyl_basis shift_b; // 1D Basis for the DG shift. + struct gkyl_array *shift_dg; // DG shift. + + int *num_do; // Number of donors at each cell in shear_dir; + int *shift_dir_idx_do; // Indices of donor cells, in the direction of the + // shift, for each cell in the TS grid. + + struct gkyl_twistshift_dg_kernels *kernels; // kernels for sub-cell integrals. + + // Projection object used in constructing the matrices. + struct gkyl_eval_on_nodes *ev_on_nod1d; + // Evaluations of a function at 1D nodes. + struct gkyl_array *func_nod1d; + + struct gkyl_nmat *scimat; // Subcell integral matrices. + struct gkyl_nmat *fmat; // Distribution function matrices. + struct gkyl_nmat *mm_contr; // Contribution resulting from a mat-mat mult. + + long *num_numcol_fidx_do; // 1D indexer, from a index identitying the num-numcol + // plane (in the num-numcol-num_basis space), to a + // linear index into the donor distribution function f. + + long *num_numcol_fidx_tar; // 1D indexer, from a index identitying the num-numcol + // plane (in the num-numcol-num_basis space), to a + // linear index into the target distribution function f. + + int *num_do_cum; // Cumulative number of donors up to a give cell in shear_dir; + struct gkyl_range permutted_ghost_r; // Ghost range to populate in the target + // field, with some dimensions permutted. + struct gkyl_range ghost_r; // Ghost range this BC fills. +}; + +#ifdef GKYL_HAVE_CUDA +/** + * Apply the twist-shift on the NVIDIA GPU. It assumes that periodicity along bc_dir has been + * applied to the donor field. Can be used in-place. + * + * @param up Twist-shift BC updater object. + * @param fdo Donor field. + * @param ftar Target field. + */ +void gkyl_twistshift_dg_advance_cu(struct gkyl_twistshift_dg *up, struct gkyl_array *fdo, struct gkyl_array *ftar); +#endif diff --git a/gyrokinetic/zero/twistshift_dg.c b/gyrokinetic/zero/twistshift_dg.c new file mode 100644 index 0000000000..f6cd67305e --- /dev/null +++ b/gyrokinetic/zero/twistshift_dg.c @@ -0,0 +1,2026 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Notes: +// a) Hard-coded parameters: +// - wrap_to_range: eps. +// - find_donors: delta_frac, num_test_pt. +// - find_intersect: tol, max_iter, num_steps. +// - calc_mats: shift_dir_idx_tar. +// - tol_xi: Minimum allowed spacing between the lower and +// upper xi (logical x) limits of subcell integral. +// b) Unlike the procedures described in M. Francisquez, et al. CPC 298 +// (2024) 109109, all subcell integrals are now done with variable y limits. +// This is possible once we realize that figure 4 is not drawn accurately; +// the blue lines should be separated by Delta y at all points. +// c) This updater only works on 5D distributions. Likely only minor changes +// are needed to make it work in other dimensions. +// d) 99% of the code is written to support a BC, a shift and shear in any +// direction. Maybe the only thing that needs to change is the permutted +// range and its use. +// +// List of functions used in computing sub-cell integrals (scimat). +// - ts_grid_cell_boundary_in_dir: cell boundary coordinate in given dir. +// - ts_grid_cell_boundaries: get all cell boundary coords. +// - ts_p2l: physical to logical transform. +// - ts_interval_dx_and_xc: compute length and center of an interval. +// - ts_grid_length_in_dir: length of the grid in given dir. +// - ts_wrap_to_range: wrap a number to a range assuming periodicity. +// - ts_shift_dir_idx_do_linidx: linear index to first donor of a given target +// cell in shift_dir_idx_do. +// - ts_check_shifted_test_point: evaluate a shifted point's cell as a +// potential donor cell. +// - ts_find_donors: find and record the donor cells for each target. +// - ts_root_find: Finds the root of a given function. +// - ts_shifted_coord_loss_func: Loss function used to find where yTar-S +// intersects yDo. +// - ts_sign: return the sign of a double. +// - ts_ts_donor_target_offset: offset between donor and target cells. +// - ts_find_intersect: finds the intersection of yTar-S and yDo. +// - ts_comp_to_phys: transform a computational to a physical coord. +// - ts_nod2mod_proj_1d: evaluate a 1D function at nodes and do a n2m transform +// to get the coefficients of the DG representation. +// - ts_integral_xlimdg: subcell integral with variable x limits. +// - ts_integral_ylimdg: subcell integral with variable y limits. +// - ts_integral_fullcelllimdg: integral over the whole cell. +// - ts_one: return 1 (for projections). +// - ts_minus_one: return -1 (for projections). +// - ts_shift_coord_shifted_log: coordinate in shift_dir shifted and transformed +// to logical space. +// - ts_subcellint_sNi_sNii: subcell integral sNi or sNii. +// - ts_subcellint_si_sii: subcell integral si or sii. +// - ts_subcellint_siii_siv: subcell integral siii or siv. +// - ts_subcellint_sv_svi: subcell integral sv or svi. +// - ts_subcellint_svii_sviii: subcell integral svii or sviii. +// - ts_subcellint_six_sx: subcell integral six or sx. +// - ts_subcellint_sxi_sxii: subcell integral sxi or sxii. +// - ts_subcellint_sxiii_sxiv: subcell integral sxiii or sxiv. +// - ts_subcellint_sxv_sxvi: subcell integral sxv or sxvi. +// - ts_calc_mats: create scimat with the result of the subcell integrals. +// +// Two additional helper functions: +// - ts_calc_num_numcol_fidx_do: index map to populate fmat with donors. +// - ts_calc_num_numcol_fidx_tar: index map to get mat-mat mult results. + +// Option to use the user-provided function describing the shift +// or a DG representation of it: +// = 0 DG representation (default and preferred). +// = 1 user-provided shift function. +// Note: the kernels that ultimately perform the integrals +// always use the DG representation. +#define shift_func_op 0 + +// Minimum allowed spacing between the lower and +// upper xi (logical x) limits of subcell integral. +#define tol_xi 1.0e-15 + +// Indices in 4-element cell boundary array. +#define cellb_lo(dir) (2*dir) +#define cellb_up(dir) (2*dir+1) + +double +ts_grid_cell_boundary_in_dir(struct gkyl_rect_grid *grid, const int *idx, enum gkyl_edge_loc edge, int dir) +{ + // Get the coordinate of the cell boundary in specified direction. + double xc[grid->ndim]; + gkyl_rect_grid_cell_center(grid, idx, xc); + return edge == GKYL_LOWER_EDGE? xc[dir]-0.5*grid->dx[dir] : xc[dir]+0.5*grid->dx[dir]; +} + +void +ts_grid_cell_boundaries(struct gkyl_rect_grid *grid, const int *idx, double *cell_bounds) +{ + // Get the cell boundaries in every dimension. The array cell_bounds + // must be a 2*grid->ndim array. + for (int d=0; dndim; d++) { + cell_bounds[d*2] = ts_grid_cell_boundary_in_dir(grid, idx, GKYL_LOWER_EDGE, d); + cell_bounds[d*2+1] = ts_grid_cell_boundary_in_dir(grid, idx, GKYL_UPPER_EDGE, d); + } +} + +static inline double +ts_p2l(double coord, double cell_center, double dx) +{ + // Transform a physical coordinate (coord) to the [-1,1] logical + // space in a cell centered at cell_center and with length dx. + return 2.0*(coord - cell_center)/dx; +} + +// Evaluation of the shift through the DG representation. +static inline void +ts_shift_dg_eval(double t, const double *coord, double *fout, void *ctx) +{ + struct ts_shift_dg_eval_ctx *tsectx = ctx; + + int cell_idx[GKYL_MAX_DIM]; + gkyl_rect_grid_coord_idx(tsectx->shear_grid, coord, cell_idx); + // Ensure that we do not go outside of the range + // (it does sometimes if x=x_max,x_min). + cell_idx[0] = fmin(cell_idx[0], tsectx->shear_r->upper[0]); + cell_idx[0] = fmax(cell_idx[0], tsectx->shear_r->lower[0]); + + double xc[GKYL_MAX_DIM]; + gkyl_rect_grid_cell_center(tsectx->shear_grid, cell_idx, xc); + + long shift_loc = gkyl_range_idx(tsectx->shear_r, cell_idx); + double *shift_c = (double *) gkyl_array_fetch(tsectx->shift_dg, shift_loc); + double xp = ts_p2l(coord[0], xc[0], tsectx->shear_grid->dx[0]); + + fout[0] = tsectx->shift_b->eval_expand(&(double) {xp}, shift_c); +} + +void +ts_interval_dx_and_xc(const double *interval, double *dx, double *xc) +{ + // Compute the lenth (dx) and center (xc) of [interval[0], interval[1]]. + double lo = interval[0], up = interval[1]; + dx[0] = up - lo; + xc[0] = 0.5*(up + lo); +} + +static inline double +ts_grid_length_in_dir(struct gkyl_rect_grid *grid, int dir) +{ + return grid->upper[dir] - grid->lower[dir]; +} + +double +ts_wrap_to_range(double val, double lower, double upper, bool pick_upper) +{ + // Wrap a number to range [lower,upper]. If pickUpper=true, output upper when + // val is a multiple of upper. Otherwise multiples of upper wrap to lower. + double L = upper - lower; + double disp = fmod(val - lower, L); + double vwrapped = lower + fmod(L + disp, L); + double eps = 1.e-12; + if ( (lower-eps < vwrapped && vwrapped < lower + eps) || + (upper-eps < vwrapped && vwrapped < upper + eps) ) { + if (pick_upper) + return upper; + else + return lower; + } + else + return vwrapped; +} + +long +ts_shift_dir_idx_do_linidx(const int *num_do, int shear_dir_idx, int shift_dir_idx, + int shift_dir_num_cells, int shear_r_lower) +{ + // Return the linear index to the first donor for the idx=(i,j) target cell, + // in the shift_dir_idx_do array. We assume shift_dir_idx_do (whose dimensions + // are Nx,Ny,num_do(i)) is in row-major order, and that it has num_do donors + // at each cell in the shear_dir_in_ts_grid direction. + long linc = 0; + // Count the number of donors in cells with an idx in the shear dir lower + // than this one. NOTE: the -1 here is because the idx is often 1-index + // (since ghost cells are the 0th index) but num_do is only defined on the + // local range. + for (int i=0; ishear_dir_in_ts_grid]}; + int shift_idx[] = {idx[up->shift_dir_in_ts_grid]}; + + int *shift_dir_idx_do_buff_ptr = (int *) gkyl_mem_buff_data(shift_dir_idx_do_buff); + + // Evaluate the shift at this test point. + double test_pt_in_shear_dir = test_pt[up->shear_dir_in_ts_grid]; + double xc_in_shear_dir = xc[up->shear_dir_in_ts_grid]; + double dx_in_shear_dir = dx[up->shear_dir_in_ts_grid]; + double shift_at_pt = up->shift_b.eval_expand( + &(double) {ts_p2l(test_pt_in_shear_dir, xc_in_shear_dir, dx_in_shear_dir)}, shift_c); + + // Find the index of the cell that owns the shifted point. + double shifted_test_pt[] = { ts_wrap_to_range(test_pt[up->shift_dir_in_ts_grid] - shift_at_pt, + up->ts_grid.lower[up->shift_dir_in_ts_grid], up->ts_grid.upper[up->shift_dir_in_ts_grid], + false) }; // Shifted test point. + int shift_dir_idx_test_pt[1]; +// gkyl_rect_grid_coord_idx(&up->shift_grid, shifted_test_pt, shift_dir_idx_test_pt); + bool pick_lower_arr[] = {pick_lower}; + gkyl_rect_grid_find_cell(&up->shift_grid, shifted_test_pt, pick_lower_arr, (int[]) {-1}, shift_dir_idx_test_pt); + + // Get the linear index to the list of donors for this target. + long linidx = ts_shift_dir_idx_do_linidx(up->num_do, + shear_idx[0], shift_idx[0], up->ts_grid.cells[up->shift_dir_in_ts_grid], up->shear_r.lower[0]); + // If this donor is not in our list of donors, include it. + bool donor_not_found = true; + for (int k=0; k 0) { + // Insert one more int. Only if num_do_curr>0 because we already + // allocated space for the first donor. + size_t new_buff_sz = gkyl_mem_buff_size(shift_dir_idx_do_buff) + sizeof(int); + shift_dir_idx_do_buff = gkyl_mem_buff_resize(shift_dir_idx_do_buff, new_buff_sz); + } + + // Get the pointer again in case it changed. + shift_dir_idx_do_buff_ptr = (int *) gkyl_mem_buff_data(shift_dir_idx_do_buff); + shift_dir_idx_do_buff_ptr[linidx+num_do_curr[0]] = shift_dir_idx_test_pt[0]; + + num_do_curr[0] += 1; + } +} + +void +ts_find_donors(struct gkyl_twistshift_dg *up) +{ + // Find the donor cells for each target cell in the TS grid. + + double delta_frac = 1.e-9; // Distance away from the boundary, as fraction of cell length. + int num_test_pt[2] = {10, 10}; // Number of test points taken along each side of the cell. + + double step_sz[2] = {0.0}; // Size of the step between test points. + double delta[2] = {0.0}; // Space between cell boundary and test points. + for (int d=0; d<2; d++) { + delta[d] = delta_frac*up->ts_grid.dx[d]; + step_sz[d] = (up->ts_grid.dx[d] - 2.0*delta[d])/(num_test_pt[d]-1); + } + + // Number of donors at each cell of the shear direction. + up->num_do = (int*) gkyl_malloc(up->shear_r.volume * sizeof(int)); + for (int i=0; ishear_r.volume; i++) + up->num_do[i] = -1; + + // Temporary buffer to store donors at (resized below). + size_t curr_buff_sz = up->ts_r.volume * sizeof(int); + gkyl_mem_buff shift_dir_idx_do_buff = gkyl_mem_buff_new(curr_buff_sz); + + int idx[] = {up->shear_r.lower[0]}; + long linidx = gkyl_range_idx(&up->shear_r, idx); + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &up->ts_r); + while (gkyl_range_iter_next(&iter)) { + + // Get the cell boundaries and cell center. + double cell_b[4] = {0.0}; // Cell boundaries, x lo and up, y lo and up; + double xc[2] = {0.0}; // Cell center. + ts_grid_cell_boundaries(&up->ts_grid, iter.idx, cell_b); + gkyl_rect_grid_cell_center(&up->ts_grid, iter.idx, xc); + + int shear_idx[] = {iter.idx[up->shear_dir_in_ts_grid]}; + int shift_idx[] = {iter.idx[up->shift_dir_in_ts_grid]}; + long shift_loc = gkyl_range_idx(&up->shear_r, shear_idx); + double *shift_c = (double *) gkyl_array_fetch(up->shift_dg, shift_loc); + + int num_do_curr = 0; + + for (int dC=0; dC<2; dC++) { // dC=0: x=const, dC=1: y=const (boundaries). + for (int xS=0; xS<2; xS++) { // xS=0: lower, xS=1 upper (boundary). + + double test_pt[2] = {0.0}; // Test point to shift. + for (int d=0; d<2; d++) + test_pt[d] = cell_b[2*d]+delta[d]; + + // Search first shifted point. Use pick_lower=false in find_cell unless + // searching for points along a x=const line near the upper y-boundary. + bool pick_lower = dC==0 && xS==1; + test_pt[dC] += xS*(up->ts_grid.dx[dC]-2.0*delta[dC]); + + // Shift the test point, find the cell that contains it, and if we + // haven't included it yet, add it to our list of donors. + ts_check_shifted_test_point(up, test_pt, xc, up->ts_grid.dx, + shift_c, iter.idx, pick_lower, &num_do_curr, shift_dir_idx_do_buff); + + // Search for other shifted points along this line. + int step_dim = (dC+1) % 2; + for (int sI=1; sIts_grid.dx, + shift_c, iter.idx, pick_lower, &num_do_curr, shift_dir_idx_do_buff); + } + } + } + + up->num_do[shear_idx[0]-up->shear_r.lower[0]] = num_do_curr; + } + + // Copy the donor list to the persistent object and release the buffer. + size_t buff_sz = gkyl_mem_buff_size(shift_dir_idx_do_buff); + up->shift_dir_idx_do = (int *) gkyl_malloc(buff_sz); + int *shift_dir_idx_do_buff_ptr = (int *) gkyl_mem_buff_data(shift_dir_idx_do_buff); + memcpy(up->shift_dir_idx_do, shift_dir_idx_do_buff_ptr, buff_sz); + gkyl_mem_buff_release(shift_dir_idx_do_buff); +} + +struct gkyl_qr_res +ts_root_find(double (*func)(double,void*), void *ctx, const double *lims, int max_iter, double tol) +{ + // Use a Ridder's root finder to find the root of func in the interval + // [lims[0],lims[1]] down to a tolerance 'tol'. Return the interval limit + // if the function is smaller than the tolerance there. Return nil if the + // function does not change sign in the interval (interval doesn't contain the root). + double funcLo = func(lims[0], ctx), funcUp = func(lims[1], ctx); +// if (fabs(funcLo) < tol) +// return (struct gkyl_qr_res) {.res=lims[0], .status=0, .nevals=2}; +// else if (fabs(funcUp) < tol) +// return (struct gkyl_qr_res) {.res=lims[1], .status=0, .nevals=2}; +// else { +// if (funcLo*funcUp < 0) +// return gkyl_ridders(func, ctx, lims[0], lims[1], funcLo, funcUp, max_iter, tol); +// else +// return (struct gkyl_qr_res) {.status=1, .nevals=2}; +// } + if (fabs(funcLo) > tol && fabs(funcUp) > tol) { + if (funcLo*funcUp < 0) + return gkyl_ridders(func, ctx, lims[0], lims[1], funcLo, funcUp, max_iter, tol); + else + return (struct gkyl_qr_res) {.status=1, .nevals=2}; + } + else if (fabs(funcLo) < tol && fabs(funcUp) < tol) + return (struct gkyl_qr_res) {.status=1, .nevals=2}; + else if (fabs(funcLo) < tol) + return (struct gkyl_qr_res) {.res=lims[0], .status=0, .nevals=2}; + else if (fabs(funcUp) < tol) + return (struct gkyl_qr_res) {.res=lims[1], .status=0, .nevals=2}; + return (struct gkyl_qr_res) {.status=1, .nevals=2}; +} + +struct ts_shifted_coord_loss_func_ctx { + double shiftCoordTar; // Target coordinate in shift_dir. + double shiftCoordDo; // Donor coordinate in shift_dir. + double shiftDirL; // Length of the domain in shift_dir. + int periodicCopyIdx; // Used to search a periodic copy of the domain (signed). + evalf_t shift_func; // Function defining the shift. + void *shift_func_ctx; // Context for shift_func. +}; + +double ts_shifted_coord_loss_func(double shearCoord, void *ctx) +{ + // Loss function used to find the shear coord. + struct ts_shifted_coord_loss_func_ctx *tsctx = ctx; + + double shift; + tsctx->shift_func(0.0, (double[]){shearCoord}, &shift, tsctx->shift_func_ctx); + + return tsctx->shiftCoordTar - shift + - (tsctx->shiftCoordDo - tsctx->periodicCopyIdx * tsctx->shiftDirL); +} + +int static inline +ts_sign(double a) +{ + if (a < 0.0) + return -1; + else if (a > 0.0) + return 1; + else + return 0; +} + +double +ts_donor_target_offset(struct gkyl_twistshift_dg *up, const double *xc_do, const double *xc_tar) { + // y-offset between the donor and the target cell (yDo-yTar), in the direction of the shift. + // xc_do: cell center coordinates of donor cell. + // xc_tar: cell center coordinates of target cell. + int shear_dir = up->shear_dir_in_ts_grid; + int shift_dir = up->shift_dir_in_ts_grid; + double x_eval = xc_do[up->shear_dir]; + double shift; + up->shift_func(0.0, (double[]){x_eval}, &shift, up->shift_func_ctx); + + int shift_sign = ts_sign(shift); + double shift_dir_L = up->ts_grid.upper[up->shift_dir] - up->ts_grid.lower[up->shift_dir]; + + // The idea here is that we keep shifting the donor cell center until it is in a + // periodic copy of our domain which overlaps with the shifted target cell center. + double xs_shifted_do = xc_do[shift_dir]; + double xs_shifted_tar = xc_tar[shift_dir] - shift; + bool keep_shifting = true; + while (keep_shifting) { + double xs_shifted_dolo = xs_shifted_do - shift_dir_L/2.0; + double xs_shifted_doup = xs_shifted_do + shift_dir_L/2.0; + if (xs_shifted_dolo <= xs_shifted_tar && xs_shifted_tar <= xs_shifted_doup) { + keep_shifting = false; + break; + } + else + xs_shifted_do = xs_shifted_do - shift_sign*shift_dir_L; + } + return xc_tar[shift_dir] - xs_shifted_do; +} + +struct gkyl_qr_res +ts_find_intersect(struct gkyl_twistshift_dg *up, double shiftCoordTar, double shiftCoordDo, + const double *shearDirBounds, const double *shiftDirLimits) +{ + // Given a y-coordinate of the target cell (yTar), and a y-coordinate + // of the donor cell (yDo), find the x-coordinate of the point where + // the yTar-yShift(x) and y=yDo lines intersect. + // yTar: target cell y coordinate. + // yDo: donor cell y coordinate. + // xBounds: search in the interval [xBounds[1],xBounds[2]]. + // yLims: lower and upper limits of the grid. + // If y-yShift-yDo=0 has no roots, it is possible that y-yShift intersects a periodic + // copy of this domain. Check for such cases by looking for the roots of + // yTar-yShift-(yDo-N*Ly)=0 where Ly is the length of the domain along y and N is an integer. + double tol = 1.e-13; + int max_iter = 100; + + double shiftDirL = shiftDirLimits[1] - shiftDirLimits[0]; + + struct ts_shifted_coord_loss_func_ctx func_ctx = { + .shiftCoordTar = shiftCoordTar, + .shiftCoordDo = shiftCoordDo, + .shiftDirL = shiftDirL, + .periodicCopyIdx = 0, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + struct gkyl_qr_res rootfind_res = ts_root_find(ts_shifted_coord_loss_func, &func_ctx, + shearDirBounds, max_iter, tol); + + if (rootfind_res.status == 1) { + // Maybe yTar-ySh intersects y=yDo in a periodic copy of the domain. Find roots of + // yTar-ySh-(yDo-nP*Ly)=0 where Ly is the y-length of the domain and nP is an integer. + + // Evaluate yTar-ySh at some points in [xBounds.lo,xBounds.up] and obtain potential nP's. + int num_steps = 10; + double step_sz = (shearDirBounds[1]-shearDirBounds[0])/num_steps; + int nP[num_steps+1], num_unique_nP = 0; + for (int sI=0; sIshift_func(0.0, (double[]){xp}, &xp_shift, up->shift_func_ctx); + + double ex_shift = xp_shift<0.? ((shiftCoordTar-xp_shift)-shiftDirLimits[0])/shiftDirL + : (shiftDirLimits[1]-(shiftCoordTar-xp_shift))/shiftDirL; + double nP_new = ts_sign(xp_shift)*floor(fabs(ex_shift)); + // If we haven't accounted for this nP, add it to our list. + bool nP_not_found = true; + for (int n=0; nshift_b.num_basis; ++i) { + ts_comp_to_phys(1, gkyl_eval_on_nodes_fetch_node(up->ev_on_nod1d, i), + dx, xc, xmu); + func(0.0, xmu, (double *)gkyl_array_fetch(up->func_nod1d,i), func_ctx); + } + gkyl_eval_on_nodes_nod2mod(up->ev_on_nod1d, up->func_nod1d, out); +} + +void +ts_integral_xlimdg(struct gkyl_twistshift_dg *up, double sFac, const double *xLimLo, + const double *xLimUp, double yLimLo, double yLimUp, double dyDo, double yOff, + const double *ySh, struct gkyl_mat *mat_do) { + // Populate a matrix (mat_do) with a sub-cell integral that has variably x limits + // represented by a DG polynomial, and a y-integral that goes from yLimLo to yLimUp. + // up: BC updater. + // sFac: +/-1 factor to add or subtract this subcell integral. + // xLimLo: DG representation of the lower x-limit. + // xLimUp: DG representation of the upper x-limit. + // yLimLo: lower y-limit. + // yLimUp: upper y-limit. + // dyDo: Cell length along y. + // yOff: Offset along y. + // ySh: DG representation of the y shift. + // mat_do: donor matrix. + up->kernels->xlimdg(sFac, xLimLo, xLimUp, yLimLo, yLimUp, dyDo, yOff, ySh, mat_do); +} + +void +ts_integral_ylimdg(struct gkyl_twistshift_dg *up, double sFac, double xLimLo, double xLimUp, + const double *yLimLo, const double *yLimUp, double dyDo, double yOff, + const double *ySh, struct gkyl_mat *mat_do) { + // Populate a matrix (mat_do) with a sub-cell integral that has variable y limits + // represented by a DG polynomial, and a x-integral that goes from xLimLo to xLimUp. + // up: BC updater. + // sFac: +/-1 factor to add or subtract this subcell integral. + // xLimLo: lower x-limit. + // xLimUp: upper x-limit. + // yLimLo: DG representation of the lower y-limit. + // yLimUp: DG representation of the upper y-limit. + // dyDo: Cell length along y. + // yOff: Offset along y. + // ySh: DG representation of the y shift. + // mat_do: donor matrix. + up->kernels->ylimdg(sFac, xLimLo, xLimUp, yLimLo, yLimUp, dyDo, yOff, ySh, mat_do); +} + +void +ts_integral_fullcelllimdg(struct gkyl_twistshift_dg *up, double dyDo, double yOff, + const double *ySh, struct gkyl_mat *mat_do) { + // Populate a matrix (mat_do) with the full-cell integral. + // up: BC updater. + // sFac: +/-1 factor to add or subtract this subcell integral. + // dyDo: Cell length along y. + // yOff: Offset along y. + // ySh: DG representation of the y shift. + // mat_do: donor matrix. + up->kernels->fullcell(dyDo, yOff, ySh, mat_do); +} + +static inline void +ts_one(double t, const double *xn, double *fout, void *ctx) +{ + fout[0] = 1.0; +} + +static inline void +ts_minus_one(double t, const double *xn, double *fout, void *ctx) +{ + fout[0] = -1.0; +} + +struct ts_shift_coord_shifted_log_ctx { + double shift_coord_tar; // Target coordinate in shift_dir. + int shift_sign_fac; + const double *xc_do, *xc_tar; // Cell centers (donor and target). + double *dx; // Cell lengths. + bool pick_upper; + int shear_dir, shift_dir; // Shear and shift directions. + double shift_dir_bounds[2]; // Domain boundaries in shift_dir. + evalf_t shift_func; // Function defining the shift. + void *shift_func_ctx; // Context for shift_func. +}; + +void +ts_shift_coord_shifted_log(double t, const double *xn, double *fout, void *ctx) +{ + // Given a logical space x coordinate (xi) and a (physical) y-coordinate in the target cell, + // compute the shifted y-coordinate in the logical space of the donor cell (eta \in [-1,1]). + // xi: logical space x coordinate. + // yTar: physical y-coordinate in target cell. + // pmSh: factor multiplying the y-shift (+/- 1). + // xcDo: cell center coordinates of donor cell. + // xcTar: cell center coordinates of target cell. + // dx: cell lengths. + // pickUpper: boolean indicating if wrapping function should return upper/lower boundary. + + double xi = xn[0]; + + struct ts_shift_coord_shifted_log_ctx *tsctx = ctx; + double shift_coord_tar = tsctx->shift_coord_tar; + int shift_sign_fac = tsctx->shift_sign_fac; + const double *xc_do = tsctx->xc_do, *xc_tar = tsctx->xc_tar; + double *dx = tsctx->dx; + bool pick_upper = tsctx->pick_upper; + int shear_dir = tsctx->shear_dir, shift_dir = tsctx->shift_dir; + double *shift_dir_bounds = tsctx->shift_dir_bounds; + + double shear_coord_phys = xc_tar[shear_dir] + 0.5*dx[shear_dir]*xi; + double shift; + tsctx->shift_func(0.0, (double[]){shear_coord_phys}, &shift, tsctx->shift_func_ctx); + + double shift_coord_shifted = shift_coord_tar - shift_sign_fac * shift; + shift_coord_shifted = ts_wrap_to_range(shift_coord_shifted, shift_dir_bounds[0], shift_dir_bounds[1], pick_upper); + + fout[0] = ts_p2l(shift_coord_shifted, xc_do[shift_dir], dx[shift_dir]); +} + +void +ts_subcellint_sNi_sNii(struct gkyl_twistshift_dg *up, struct ts_val_found *inter_pts, + const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, + bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +{ + // Perform sNi or sNii subcell integrals. + // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). + // xc_do: cell center of donor cell. + // xc_tar: cell center of target cell. + // cellb_do: boundaries of target cell. + // cellb_tar: boundaries of target cell. + // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? + // shift_c: DG coefficients of the shift. + // mat_do: current donor matrix. + + bool is_sNi = inter_pts[2].value < inter_pts[0].value; + + struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { + .shift_sign_fac = 1, + .xc_do = xc_do, + .xc_tar = xc_tar, + .dx = up->ts_grid.dx, + .pick_upper = is_upper_shift_dir_cell, + .shear_dir = up->shear_dir_in_ts_grid, + .shift_dir = up->shift_dir_in_ts_grid, + .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + + double xi_b[2]; // Limits of xi integral. + evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. + double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; + + // Offset between cell centers in direction of the shift. + double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); + + if (is_sNi) { + // sNi + // 1) Add the contribution of the left portion. + xi_b[0] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + + // 2) Add the contribution of the right portion. + xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + } + else { + // sNii + // 1) Add the contribution of the left portion. + xi_b[0] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + + // 2) Add the contribution of the right portion. + xi_b[0] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + } +} + +void +ts_subcellint_si_sii(struct gkyl_twistshift_dg *up, struct ts_val_found *inter_pts, + const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, + bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +{ + // Perform subcell integral si or sii, using fixed x-limits and variable y limits. + // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). + // xc_do: cell center of donor cell. + // xc_tar: cell center of target cell. + // cellb_do: boundaries of target cell. + // cellb_tar: boundaries of target cell. + // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? + // shift_c: DG coefficients of the shift. + // mat_do: current donor matrix. + + double x_up = cellb_tar[cellb_up(up->shear_dir_in_ts_grid)]; + double x_lo = cellb_tar[cellb_lo(up->shear_dir_in_ts_grid)]; + double shift_lo, shift_up; + + up->shift_func(0.0, (double[]){x_lo}, &shift_lo, up->shift_func_ctx); + up->shift_func(0.0, (double[]){x_up}, &shift_up, up->shift_func_ctx); + + bool is_si = -shift_lo < -shift_up; + + double xi_b[2]; // Limits of xi integral. + if (is_si) { + // si integral. + xi_b[0] = -1.0; + xi_b[1] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]);; + } + else { + // sii integral. + xi_b[0] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]);; + xi_b[1] = 1.0; + } + + evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + + struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { + .shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)], + .shift_sign_fac = 1, + .xc_do = xc_do, + .xc_tar = xc_tar, + .dx = up->ts_grid.dx, + .pick_upper = is_upper_shift_dir_cell, + .shear_dir = up->shear_dir_in_ts_grid, + .shift_dir = up->shift_dir_in_ts_grid, + .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + + double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + // Offset between cell centers in direction of the shift. + double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); +} + +void +ts_subcellint_siii_siv(struct gkyl_twistshift_dg *up, struct ts_val_found *inter_pts, + const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, + bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +{ + // Perform subcell integral siii or siv, using fixed x-limits and variable y limits. + // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). + // xc_do: cell center of donor cell. + // xc_tar: cell center of target cell. + // cellb_do: boundaries of target cell. + // cellb_tar: boundaries of target cell. + // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? + // shift_c: DG coefficients of the shift. + // mat_do: current donor matrix. + + double x_lo = cellb_tar[cellb_lo(up->shear_dir_in_ts_grid)]; + double x_up = cellb_tar[cellb_up(up->shear_dir_in_ts_grid)]; + double shift_lo, shift_up; + + up->shift_func(0.0, (double[]){x_lo}, &shift_lo, up->shift_func_ctx); + up->shift_func(0.0, (double[]){x_up}, &shift_up, up->shift_func_ctx); + + bool is_siii = -shift_lo > -shift_up; + + double xi_b[2]; // Limits of xi integral. + if (is_siii) { + // siii integral. + xi_b[0] = -1.0; + xi_b[1] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]);; + } + else { + // siv integral. + xi_b[0] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]);; + xi_b[1] = 1.0; + } + + evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + + struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { + .shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)], + .shift_sign_fac = 1, + .xc_do = xc_do, + .xc_tar = xc_tar, + .dx = up->ts_grid.dx, + .pick_upper = is_upper_shift_dir_cell, + .shear_dir = up->shear_dir_in_ts_grid, + .shift_dir = up->shift_dir_in_ts_grid, + .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + + double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + // Offset between cell centers in direction of the shift. + double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); +} + +void +ts_subcellint_sv_svi(struct gkyl_twistshift_dg *up, struct ts_val_found *inter_pts, + const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, + bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +{ + // Perform sv or svi subcell integrals. + // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). + // xc_do: cell center of donor cell. + // xc_tar: cell center of target cell. + // cellb_do: boundaries of target cell. + // cellb_tar: boundaries of target cell. + // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? + // shift_c: DG coefficients of the shift. + // mat_do: current donor matrix. + + bool is_sv = inter_pts[3].value < inter_pts[1].value; + + struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { + .shift_sign_fac = 1, + .xc_do = xc_do, + .xc_tar = xc_tar, + .dx = up->ts_grid.dx, + .pick_upper = is_upper_shift_dir_cell, + .shear_dir = up->shear_dir_in_ts_grid, + .shift_dir = up->shift_dir_in_ts_grid, + .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + + double xi_b[2]; // Limits of xi integral. + evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. + double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; + + // Offset between cell centers in direction of the shift. + double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); + + if (is_sv) { + // sv + // 1) Add the contribution of the left portion. + xi_b[0] = -1.0; + xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + + // 2) Add the contribution of the right portion. + xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + } + else { + // svi + // 1) Add the contribution of the left portion. + xi_b[0] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + + // 2) Add the contribution of the right portion. + xi_b[0] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = 1.0; + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + } +} + +void +ts_subcellint_svii_sviii(struct gkyl_twistshift_dg *up, struct ts_val_found *inter_pts, + const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, + bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +{ + // Perform svii or sviii subcell integrals. + // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). + // xc_do: cell center of donor cell. + // xc_tar: cell center of target cell. + // cellb_do: boundaries of target cell. + // cellb_tar: boundaries of target cell. + // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? + // shift_c: DG coefficients of the shift. + // mat_do: current donor matrix. + + bool is_svii = inter_pts[0].value < inter_pts[2].value; + + struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { + .shift_sign_fac = 1, + .xc_do = xc_do, + .xc_tar = xc_tar, + .dx = up->ts_grid.dx, + .pick_upper = is_upper_shift_dir_cell, + .shear_dir = up->shear_dir_in_ts_grid, + .shift_dir = up->shift_dir_in_ts_grid, + .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + + double xi_b[2]; // Limits of xi integral. + evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. + double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; + + // Offset between cell centers in direction of the shift. + double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); + + if (is_svii) { + // svii + // 1) Add the contribution of the left portion. + xi_b[0] = -1.0; + xi_b[1] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + + // 2) Add the contribution of the right portion. + xi_b[0] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + } + else { + // sviii + // 1) Add the contribution of the left portion. + xi_b[0] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + + // 2) Add the contribution of the right portion. + xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = 1.0; + + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + } +} + +void +ts_subcellint_six_sx(struct gkyl_twistshift_dg *up, struct ts_val_found *inter_pts, + const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, + bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +{ + // Perform six or sx subcell integrals. + // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). + // xc_do: cell center of donor cell. + // xc_tar: cell center of target cell. + // cellb_do: boundaries of target cell. + // cellb_tar: boundaries of target cell. + // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? + // shift_c: DG coefficients of the shift. + // mat_do: current donor matrix. + + bool is_six = inter_pts[0].value < inter_pts[1].value; + + // Limits of xi integral. + double xi_b[2]; + if (is_six) { + // six + xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + } + else { + // sx + xi_b[0] = ts_p2l(inter_pts[1].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + } + + struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { + .shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)], + .shift_sign_fac = 1, + .xc_do = xc_do, + .xc_tar = xc_tar, + .dx = up->ts_grid.dx, + .pick_upper = is_upper_shift_dir_cell, + .shear_dir = up->shear_dir_in_ts_grid, + .shift_dir = up->shift_dir_in_ts_grid, + .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + + evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + + double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + // Offset between cell centers in direction of the shift. + double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); +} + +void +ts_subcellint_sxi_sxii(struct gkyl_twistshift_dg *up, struct ts_val_found *inter_pts, + const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, + bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +{ + // Perform sxi or sxii subcell integrals. + // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). + // xc_do: cell center of donor cell. + // xc_tar: cell center of target cell. + // cellb_do: boundaries of target cell. + // cellb_tar: boundaries of target cell. + // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? + // shift_c: DG coefficients of the shift. + // mat_do: current donor matrix. + + bool is_sxi = inter_pts[3].value < inter_pts[2].value; + + // Limits of xi integral. + double xi_b[2]; + if (is_sxi) { + // six + xi_b[0] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + } + else { + // sx + xi_b[0] = ts_p2l(inter_pts[2].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + } + + struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { + .shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)], + .shift_sign_fac = 1, + .xc_do = xc_do, + .xc_tar = xc_tar, + .dx = up->ts_grid.dx, + .pick_upper = is_upper_shift_dir_cell, + .shear_dir = up->shear_dir_in_ts_grid, + .shift_dir = up->shift_dir_in_ts_grid, + .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + + evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + + double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + // Offset between cell centers in direction of the shift. + double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); +} + +void +ts_subcellint_sxiii_sxiv(struct gkyl_twistshift_dg *up, struct ts_val_found *inter_pts, + const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, + bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +{ + // Perform sxiii or sxiv subcell integrals. + // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). + // xc_do: cell center of donor cell. + // xc_tar: cell center of target cell. + // cellb_do: boundaries of target cell. + // cellb_tar: boundaries of target cell. + // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? + // shift_c: DG coefficients of the shift. + // mat_do: current donor matrix. + + double x_lo = cellb_tar[cellb_lo(up->shear_dir_in_ts_grid)]; + double x_up = cellb_tar[cellb_up(up->shear_dir_in_ts_grid)]; + double shift_lo, shift_up; + + up->shift_func(0.0, (double[]){x_lo}, &shift_lo, up->shift_func_ctx); + up->shift_func(0.0, (double[]){x_up}, &shift_up, up->shift_func_ctx); + + bool is_sxiii = -shift_lo < -shift_up; + + struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { + .shift_sign_fac = 1, + .xc_do = xc_do, + .xc_tar = xc_tar, + .dx = up->ts_grid.dx, + .pick_upper = is_upper_shift_dir_cell, + .shear_dir = up->shear_dir_in_ts_grid, + .shift_dir = up->shift_dir_in_ts_grid, + .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + + double xi_b[2]; // Limits of xi integral. + evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. + double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; + + // Offset between cell centers in direction of the shift. + double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); + + // 1) Add the contribution of the left portion. + xi_b[0] = -1.0; + xi_b[1] = ts_p2l(inter_pts[3].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + + if (is_sxiii) { + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + } + else { + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + } + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); + + // 2) Add the contribution of the right portion. + xi_b[0] = ts_p2l(inter_pts[0].value, xc_do[up->shear_dir_in_ts_grid], up->ts_grid.dx[up->shear_dir_in_ts_grid]); + xi_b[1] = 1.0; + + if (is_sxiii) { + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + } + else { + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + } + + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); +} + +void +ts_subcellint_sxv_sxvi(struct gkyl_twistshift_dg *up, struct ts_val_found *inter_pts, + const double *xc_do, const double *xc_tar, const double *cellb_do, const double *cellb_tar, + bool is_upper_shift_dir_cell, const double *shift_c, struct gkyl_mat *mat_do) +{ + // Perform subcell integral sxv or sxvi, using fixed x-limits and variable y limits. + // inter_pts: intersections y_{j_tar-/+1/2}-yShift and y_{j_do-/+1/2} (lower/upper y-boundaries of donor cell). + // xc_do: cell center of donor cell. + // xc_tar: cell center of target cell. + // cellb_do: boundaries of target cell. + // cellb_tar: boundaries of target cell. + // is_upper_shift_dir_cell: is the donor the upper cell in the shift dir? + // shift_c: DG coefficients of the shift. + // mat_do: current donor matrix. + + double xi_b[] = {-1.0, 1.0}; // Limits of xi integral. + + double shift_dir_bounds[] = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}; + + double x_eval = xc_do[up->shear_dir_in_ts_grid]; + double shift; + + up->shift_func(0.0, (double[]){x_eval}, &shift, up->shift_func_ctx); + + double shifted_coord = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)] - shift; + shifted_coord = ts_wrap_to_range(shifted_coord, shift_dir_bounds[0], shift_dir_bounds[1], + is_upper_shift_dir_cell); + + struct ts_shift_coord_shifted_log_ctx eta_lims_ctx = { + .shift_sign_fac = 1, + .xc_do = xc_do, + .xc_tar = xc_tar, + .dx = up->ts_grid.dx, + .pick_upper = is_upper_shift_dir_cell, + .shear_dir = up->shear_dir_in_ts_grid, + .shift_dir = up->shift_dir_in_ts_grid, + .shift_dir_bounds = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}, + .shift_func = up->shift_func, + .shift_func_ctx = up->shift_func_ctx, + }; + + evalf_t eta_lims[2]; // Table of functions definting the limits of eta integral. + if ( cellb_do[cellb_lo(up->shift_dir_in_ts_grid)] <= shifted_coord && + shifted_coord <= cellb_do[cellb_up(up->shift_dir_in_ts_grid)] ) { + // sxv integral. + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_lo(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_shift_coord_shifted_log; + eta_lims[1] = ts_one; + } + else { + // sxvi integral. + eta_lims_ctx.shift_coord_tar = cellb_tar[cellb_up(up->shift_dir_in_ts_grid)]; + eta_lims[0] = ts_minus_one; + eta_lims[1] = ts_shift_coord_shifted_log; + } + + double etalo_xi[up->shift_b.num_basis], etaup_xi[up->shift_b.num_basis]; + ts_nod2mod_proj_1d(up, eta_lims[0], &eta_lims_ctx, xi_b, etalo_xi); + ts_nod2mod_proj_1d(up, eta_lims[1], &eta_lims_ctx, xi_b, etaup_xi); + // Offset between cell centers in direction of the shift. + double xs_off = ts_donor_target_offset(up, xc_do, xc_tar); + + if (fabs(xi_b[1] - xi_b[0]) > tol_xi) + up->kernels->ylimdg(1.0, xi_b[0], xi_b[1], etalo_xi, etaup_xi, + up->ts_grid.dx[up->shift_dir_in_ts_grid], xs_off, shift_c, mat_do); +} + +struct gkyl_nmat * +ts_calc_mats(struct gkyl_twistshift_dg *up) +{ + + // Allocate matrices containing the discrete subcell integrals. + int num_do_tot = 0; + for (int i=0; ishear_r.volume; i++) + num_do_tot += up->num_do[i]; + + struct gkyl_nmat *matsdo = gkyl_nmat_new(num_do_tot, up->basis.num_basis, up->basis.num_basis); + for (int n=0; nnum; ++n) { + struct gkyl_mat mat = gkyl_nmat_get(matsdo, n); + for (int j=0; jnc; ++j) + for (int i=0; inr; ++i) + gkyl_mat_set(&mat, i, j, 0.0); + } + + // y-index of the reference target used to precalc matrices. For positive(negative) + // yShift idx=1(last) might be better, but ideally it shouldn't matter. + int shift_dir_idx_tar = 1; + + double shift_dir_lims[] = {up->ts_grid.lower[up->shift_dir_in_ts_grid], + up->ts_grid.upper[up->shift_dir_in_ts_grid]}; + + // Create an eval_on_nodes updater to use its nodes and functions (but not + // the whole advance method). + up->ev_on_nod1d = gkyl_eval_on_nodes_new(&up->shear_grid, &up->shift_b, 1, ts_one, NULL); + // Create an array to store evaluations of a function at 1D nodes. + up->func_nod1d = gkyl_array_new(GKYL_DOUBLE, 1, up->shift_b.num_basis); + + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &up->shear_r); + while (gkyl_range_iter_next(&iter)) { + + // Get the cell boundaries and cell center. + int idx_tar[2]; // Target index. + idx_tar[up->shift_dir_in_ts_grid] = shift_dir_idx_tar; + idx_tar[up->shear_dir_in_ts_grid] = iter.idx[0]; + double cellb_tar[4] = {0.0}; // Cell boundaries, x lo and up, y lo and up; + double xc_tar[2] = {0.0}; // Cell center. + ts_grid_cell_boundaries(&up->ts_grid, idx_tar, cellb_tar); + gkyl_rect_grid_cell_center(&up->ts_grid, idx_tar, xc_tar); + + long shift_loc = gkyl_range_idx(&up->shear_r, iter.idx); + double *shift_c = (double *) gkyl_array_fetch(up->shift_dg, shift_loc); + + long linidx_do = ts_shift_dir_idx_do_linidx(up->num_do, iter.idx[0], shift_dir_idx_tar, + up->ts_grid.cells[up->shift_dir_in_ts_grid], up->shear_r.lower[0]); + int *shift_dir_idx_do_ptr = &up->shift_dir_idx_do[linidx_do]; + + long linidx_mats_do = 0; + for (int i=0; ishear_r.lower[0]; i++) + linidx_mats_do += up->num_do[i]; + + for (int iC=0; iCnum_do[iter.idx[0]-up->shear_r.lower[0]]; iC++){ + int idx_do[2]; // Target index. + idx_do[up->shift_dir_in_ts_grid] = shift_dir_idx_do_ptr[iC]; + idx_do[up->shear_dir_in_ts_grid] = iter.idx[0]; + + double cellb_do[4] = {0.0}; // Cell boundaries, x lo and up, y lo and up; + double xc_do[2] = {0.0}; // Cell center. + ts_grid_cell_boundaries(&up->ts_grid, idx_do, cellb_do); + gkyl_rect_grid_cell_center(&up->ts_grid, idx_do, xc_do); + + // Get the matrix we are presently assigning. + struct gkyl_mat mat_do = gkyl_nmat_get(matsdo, linidx_mats_do+iC); + + // Find the points where y_{j_tar-/+1/2}-yShift intersect the y=y_{j_do-/+1/2} lines. + // Also record the number and indices of points found/not found. + struct ts_val_found inter_pts[4] = {}; + int num_inter_pts_found = 0, num_inter_pts_not_found = 4; + int inter_pts_found_idxs[4], inter_pts_not_found_idxs[4]; + for (int i=0; i<2; i++) { // Loop over j_tar-/+1/2 + for (int j=0; j<2; j++) { // Loop over j_do-/+1/2 + double shift_dir_coord_tar = cellb_tar[2*up->shift_dir_in_ts_grid+i]; + double shift_dir_coord_do = cellb_do[2*up->shift_dir_in_ts_grid+j]; + struct gkyl_qr_res inter_res = ts_find_intersect(up, shift_dir_coord_tar, shift_dir_coord_do, + (double[]) {cellb_tar[cellb_lo(up->shear_dir_in_ts_grid)],cellb_tar[cellb_up(up->shear_dir_in_ts_grid)]}, + shift_dir_lims); + int ip_linc = i*2+j; + inter_pts[ip_linc].status = inter_res.status == 0; + if (inter_res.status == 0) { + inter_pts[ip_linc].value = inter_res.res; + inter_pts_found_idxs[num_inter_pts_found] = ip_linc; + num_inter_pts_found++; + } + else { + inter_pts_not_found_idxs[num_inter_pts_not_found] = ip_linc; + num_inter_pts_not_found--; + } + } + } + + bool is_upper_shift_dir_cell = idx_do[up->shift_dir_in_ts_grid] == up->ts_grid.cells[up->shift_dir_in_ts_grid]; + + if (num_inter_pts_found == 4) { + // sN: all intersections are found at this cell. + ts_subcellint_sNi_sNii(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); + } + else if (num_inter_pts_found == 1) { + if (inter_pts[1].status) { + // si: y_{j_tar-1/2}-yShift intersects x_{i-1/2}. + // sii: y_{j_tar-1/2}-yShift intersects x_{i+1/2}. + ts_subcellint_si_sii(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); + } + else { + // siii: y_{j_tar+1/2}-yShift intersects x_{i-1/2}. + // siv: y_{j_tar+1/2}-yShift intersects x_{i+1/2}. + ts_subcellint_siii_siv(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); + } + } + else if (num_inter_pts_found == 3) { + if (!inter_pts[2].status) { + // sv: y_{j_tar+1/2}-yShift doesn't intersect y_{j_do-1/2} & intersects x_{i-1/2}. + // svi: y_{j_tar+1/2}-yShift doesn't intersect y_{j_do-1/2} & intersects x_{i+1/2}. + ts_subcellint_sv_svi(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); + } + else { + // svii: y_{j_tar-1/2}-yShift doesn't intersect y_{j_do+1/2} & intersects x_{i-1/2}. + // sviii: y_{j_tar-1/2}-yShift doesn't intersect y_{j_do+1/2} & intersects x_{i+1/2}. + ts_subcellint_svii_sviii(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); + } + } + else if (num_inter_pts_found == 2) { + if (inter_pts[0].status && inter_pts[1].status) { + // six: y_{j_tar-1/2}-yShift crosses y_{j_do-/+1/2} (increasing yShift). + // sx: y_{j_tar-1/2}-yShift crosses y_{j_do-/+1/2} (decreasing yShift). + ts_subcellint_six_sx(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); + } + else if (inter_pts[2].status && inter_pts[3].status) { + // sxi: y_{j_tar+1/2}-yShift crosses y_{j_do-/+1/2} (decreasing yShift). + // sxii: y_{j_tar+1/2}-yShift crosses y_{j_do-/+1/2} (increasing yShift). + ts_subcellint_sxi_sxii(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); + } + else { + // sxiii: y_{j_tar-1/2}-yShift crosses y_{j_do-1/2} & y_{j_tar+1/2}-yShift crosses y_{j_do+1/2} (increasing yShift). + // sxiv: y_{j_tar-1/2}-yShift crosses y_{j_do-1/2} & y_{j_tar+1/2}-yShift crosses y_{j_do+1/2} (decreasing yShift). + ts_subcellint_sxiii_sxiv(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); + } + } + else if (num_inter_pts_found == 0) { + // sxv: y_{j_tar-1/2}-yShift crosses x_{i-/+1/2}. + // sxvi: y_{j_tar+1/2}-yShift crosses x_{i-/+1/2}. + ts_subcellint_sxv_sxvi(up, inter_pts, xc_do, xc_tar, cellb_do, cellb_tar, is_upper_shift_dir_cell, shift_c, &mat_do); + } + else { + // An error occurred. This shouldn't happen. + assert(false); + } + } + + } + + gkyl_array_release(up->func_nod1d); + gkyl_eval_on_nodes_release(up->ev_on_nod1d); + + struct gkyl_nmat *matsdo_out = up->use_gpu? gkyl_nmat_cu_dev_new(matsdo->num, matsdo->nr, matsdo->nc) + : gkyl_nmat_acquire(matsdo); + gkyl_nmat_copy(matsdo_out, matsdo); + gkyl_nmat_release(matsdo); + + return matsdo_out; +} + +long * +ts_calc_num_numcol_fidx_do(struct gkyl_twistshift_dg *up) +{ + // Calculate the linear indices into the donor distribution function gkyl_array + // for each num-numcol plane (in the num-numcol-num_basis) space. + + long *num_numcol_fidx_do_ho = (long*) gkyl_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); + + // Location in the direction of the BC from which to take donor + // distributions. We assume that the user filled the ghost cell with the skin + // on the other side (i.e. applied periodicity first). + int bc_dir_loc_do = up->edge == GKYL_LOWER_EDGE? up->local_bcdir_ext_r.lower[up->bc_dir] + : up->local_bcdir_ext_r.upper[up->bc_dir]; + + // Range over directions other than shear and bc dirs. + struct gkyl_range shearbc_perp_r; + int remove[GKYL_MAX_DIM] = {0}, loc_in_dir[GKYL_MAX_DIM] = {0};; + remove[up->shear_dir] = remove[up->bc_dir] = 1; + loc_in_dir[up->shear_dir] = up->local_bcdir_ext_r.lower[up->shear_dir]; + loc_in_dir[up->bc_dir] = bc_dir_loc_do; + gkyl_range_deflate(&shearbc_perp_r, &up->local_bcdir_ext_r, remove, loc_in_dir); + + int shift_dir_in_shearbc_perp_r; + if (up->shift_dir < up->shear_dir && up->shift_dir < up->bc_dir) + shift_dir_in_shearbc_perp_r = up->shift_dir; + else if (up->shift_dir > up->shear_dir && up->shift_dir > up->bc_dir) + shift_dir_in_shearbc_perp_r = up->shift_dir-2; + else + shift_dir_in_shearbc_perp_r = up->shift_dir-1; + + int prev_shift_dir_idx = 0; + int donor_count = 0; + int do_idx[up->local_bcdir_ext_r.ndim]; + + // Loop over directions perpendicular to shear and BC dirs. + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &shearbc_perp_r); + while (gkyl_range_iter_next(&iter)) { + int ic = 0; + for (int d=0; dlocal_bcdir_ext_r.ndim; d++) { + if (d != up->bc_dir && d != up->shear_dir) { + do_idx[d] = iter.idx[ic]; + ic++; + } + } + do_idx[up->bc_dir] = bc_dir_loc_do; + + struct gkyl_range_iter shear_dir_iter; + gkyl_range_iter_init(&shear_dir_iter, &up->shear_r); + while (gkyl_range_iter_next(&shear_dir_iter)) { + + int shear_dir_idx = shear_dir_iter.idx[0]; + + long linidx_do = ts_shift_dir_idx_do_linidx(up->num_do, shear_dir_idx, + iter.idx[shift_dir_in_shearbc_perp_r], up->ts_grid.cells[up->shift_dir_in_ts_grid], up->shear_r.lower[0]); + + for (int i = 0; i < up->num_do[shear_dir_idx-up->shear_r.lower[0]]; i++) { + do_idx[up->shear_dir] = shear_dir_idx; + do_idx[up->shift_dir] = up->shift_dir_idx_do[linidx_do+i]; + + long loc = gkyl_range_idx(&up->local_bcdir_ext_r, do_idx); + num_numcol_fidx_do_ho[donor_count] = loc; + + donor_count += 1; + } + } + prev_shift_dir_idx = iter.idx[shift_dir_in_shearbc_perp_r]; + } + + long *num_numcol_fidx_do; + if (!up->use_gpu) { + num_numcol_fidx_do = (long*) gkyl_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); + memcpy(num_numcol_fidx_do, num_numcol_fidx_do_ho, up->fmat->num * up->fmat->nc * sizeof(long)); + } +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + num_numcol_fidx_do = (long*) gkyl_cu_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); + gkyl_cu_memcpy(num_numcol_fidx_do, num_numcol_fidx_do_ho, up->fmat->num * up->fmat->nc * sizeof(long), GKYL_CU_MEMCPY_H2D); + } +#endif + + gkyl_free(num_numcol_fidx_do_ho); + + return num_numcol_fidx_do; +} + +long * +ts_calc_num_numcol_fidx_tar(struct gkyl_twistshift_dg *up) +{ + + long *num_numcol_fidx_tar_ho = (long*) gkyl_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); + + // Location in the direction of the BC in which to place the target + // distributions. We assume that the local_bcdir_ext_r is a range extended in z (it + // includes the z ghose cell). + int bc_dir_loc_tar = up->edge == GKYL_LOWER_EDGE? up->local_bcdir_ext_r.lower[up->bc_dir] + : up->local_bcdir_ext_r.upper[up->bc_dir]; + + // Range over directions other than shear and bc dirs. + struct gkyl_range shearbc_perp_r; + int remove[GKYL_MAX_DIM] = {0}, loc_in_dir[GKYL_MAX_DIM] = {0};; + remove[up->shear_dir] = remove[up->bc_dir] = 1; + loc_in_dir[up->shear_dir] = up->local_bcdir_ext_r.lower[up->shear_dir]; + loc_in_dir[up->bc_dir] = bc_dir_loc_tar; + gkyl_range_deflate(&shearbc_perp_r, &up->local_bcdir_ext_r, remove, loc_in_dir); + + int tar_idx[up->local_bcdir_ext_r.ndim]; + int tar_count = 0; + + // Loop over directions perpendicular to shear and BC dirs. + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &shearbc_perp_r); + while (gkyl_range_iter_next(&iter)) { + + int ic = 0; + for (int d=0; dlocal_bcdir_ext_r.ndim; d++) { + if (d != up->bc_dir && d != up->shear_dir) { + tar_idx[d] = iter.idx[ic]; + ic++; + } + } + tar_idx[up->bc_dir] = bc_dir_loc_tar; + + + struct gkyl_range_iter shear_dir_iter; + gkyl_range_iter_init(&shear_dir_iter, &up->shear_r); + while (gkyl_range_iter_next(&shear_dir_iter)) { + int shear_dir_idx = shear_dir_iter.idx[0]; + tar_idx[up->shear_dir] = shear_dir_idx; + + long loc = gkyl_range_idx(&up->local_bcdir_ext_r, tar_idx); + num_numcol_fidx_tar_ho[tar_count] = loc; + tar_count += 1; + } + } + + long *num_numcol_fidx_tar; + if (!up->use_gpu) { + num_numcol_fidx_tar = (long*) gkyl_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); + memcpy(num_numcol_fidx_tar, num_numcol_fidx_tar_ho, up->fmat->num * up->fmat->nc * sizeof(long)); + } +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + num_numcol_fidx_tar = (long*) gkyl_cu_malloc(up->fmat->num * up->fmat->nc * sizeof(long)); + gkyl_cu_memcpy(num_numcol_fidx_tar, num_numcol_fidx_tar_ho, up->fmat->num * up->fmat->nc * sizeof(long), GKYL_CU_MEMCPY_H2D); + } +#endif + + gkyl_free(num_numcol_fidx_tar_ho); + + return num_numcol_fidx_tar; +} + +void +gkyl_twistshift_dg_choose_kernels(struct gkyl_basis basis, int cdim, int shift_poly_order, + struct gkyl_twistshift_dg_kernels *kers) +{ + int dim = basis.ndim; + int vdim = dim - cdim; + enum gkyl_basis_type basis_type = basis.b_type; + int poly_order = basis.poly_order; + switch (basis_type) { + case GKYL_BASIS_MODAL_GKHYBRID: + case GKYL_BASIS_MODAL_SERENDIPITY: + if (shift_poly_order == 1) { + kers->xlimdg = vdim==0? ser_twistshift_xlimdg_list_0v_yShp1[cdim-2].kernels[poly_order] + : ser_twistshift_xlimdg_list_2v_yShp1[cdim-2].kernels[poly_order]; + kers->ylimdg = vdim==0? ser_twistshift_ylimdg_list_0v_yShp1[cdim-2].kernels[poly_order] + : ser_twistshift_ylimdg_list_2v_yShp1[cdim-2].kernels[poly_order]; + kers->fullcell = vdim==0? ser_twistshift_fullcell_list_0v_yShp1[cdim-2].kernels[poly_order] + : ser_twistshift_fullcell_list_2v_yShp1[cdim-2].kernels[poly_order]; + } + else if (shift_poly_order == 2) { + assert(false); // MF 2025/09/20: removed 3x2v kernel because it's 8.5 MB. + kers->xlimdg = vdim==0? ser_twistshift_xlimdg_list_0v_yShp2[cdim-2].kernels[poly_order] + : ser_twistshift_xlimdg_list_2v_yShp2[cdim-2].kernels[poly_order]; + kers->ylimdg = vdim==0? ser_twistshift_ylimdg_list_0v_yShp2[cdim-2].kernels[poly_order] + : ser_twistshift_ylimdg_list_2v_yShp2[cdim-2].kernels[poly_order]; + kers->fullcell = vdim==0? ser_twistshift_fullcell_list_0v_yShp2[cdim-2].kernels[poly_order] + : ser_twistshift_fullcell_list_2v_yShp2[cdim-2].kernels[poly_order]; + } + return; + default: + assert(false); + break; + } +} + +struct gkyl_twistshift_dg* +gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp) +{ + + // Allocate space for new updater. + struct gkyl_twistshift_dg *up = gkyl_malloc(sizeof(struct gkyl_twistshift_dg)); + + up->bc_dir = inp->bc_dir; + up->shift_dir = inp->shift_dir; + up->shear_dir = inp->shear_dir; + up->edge = inp->edge; + up->basis = inp->basis; + up->grid = inp->grid; + up->use_gpu = inp->use_gpu; + up->local_bcdir_ext_r = inp->bcdir_ext_update_r; + + // Assume the poly order of the DG shift is the same as that of the field, + // unless requested otherwise. + up->shift_poly_order = inp->basis.poly_order; + if (inp->shift_poly_order) + up->shift_poly_order = inp->shift_poly_order; + + const int ndim = inp->bcdir_ext_update_r.ndim; + // Check that it is being used for 3D or 5D. Likely only small changes are + // needed to make it work in other dimensions. + assert(ndim == 3 || ndim == 5); + + double lo1d[1], up1d[1]; int cells1d[1]; + + // Create 1D grid and range in the direction of the shear. + gkyl_range_init(&up->shear_r, 1, (int[]) {up->local_bcdir_ext_r.lower[inp->shear_dir]}, + (int[]) {up->local_bcdir_ext_r.upper[inp->shear_dir]}); + lo1d[0] = inp->grid.lower[up->shear_dir]; + up1d[0] = inp->grid.upper[up->shear_dir]; + cells1d[0] = inp->grid.cells[up->shear_dir]; + gkyl_rect_grid_init(&up->shear_grid, 1, lo1d, up1d, cells1d); + int idx[] = {up->shear_r.lower[0]}; + long linidx = gkyl_range_idx(&up->shear_r, idx); + + // Create 1D grid and range in the diretion of the shift. + gkyl_range_init(&up->shift_r, 1, (int[]) {up->local_bcdir_ext_r.lower[inp->shift_dir]}, + (int[]) {up->local_bcdir_ext_r.upper[inp->shift_dir]}); + lo1d[0] = inp->grid.lower[up->shift_dir]; + up1d[0] = inp->grid.upper[up->shift_dir]; + cells1d[0] = inp->grid.cells[up->shift_dir]; + gkyl_rect_grid_init(&up->shift_grid, 1, lo1d, up1d, cells1d); + + // Create 2D grid (and range) the twist-shift takes place in. + int dimlo, dimup; + if (up->shift_dir < up->shear_dir) { + dimlo = up->shift_dir; + dimup = up->shear_dir; + up->shift_dir_in_ts_grid = 0; + up->shear_dir_in_ts_grid = 1; + } + else { + dimlo = up->shear_dir; + dimup = up->shift_dir; + up->shift_dir_in_ts_grid = 1; + up->shear_dir_in_ts_grid = 0; + } + gkyl_range_init(&up->ts_r, 2, (int[]) {up->local_bcdir_ext_r.lower[dimlo], up->local_bcdir_ext_r.lower[dimup]}, + (int[]) {up->local_bcdir_ext_r.upper[dimlo], up->local_bcdir_ext_r.upper[dimup]}); + double lo2d[] = {inp->grid.lower[dimlo], inp->grid.lower[dimup]}; + double up2d[] = {inp->grid.upper[dimlo], inp->grid.upper[dimup]}; + int cells2d[] = {inp->grid.cells[dimlo], inp->grid.cells[dimup]}; + gkyl_rect_grid_init(&up->ts_grid, 2, lo2d, up2d, cells2d); + + // Project the shift onto the shift basis. + gkyl_cart_modal_serendip(&up->shift_b, 1, up->shift_poly_order); + if (inp->shift_func) { + up->shift_dg = gkyl_array_new(GKYL_DOUBLE, up->shift_b.num_basis, up->shear_r.volume); + gkyl_eval_on_nodes *evup = gkyl_eval_on_nodes_new(&up->shear_grid, &up->shift_b, 1, + inp->shift_func, inp->shift_func_ctx); + gkyl_eval_on_nodes_advance(evup, 0.0, &up->shear_r, up->shift_dg); + gkyl_eval_on_nodes_release(evup); + } + else { + up->shift_dg = gkyl_array_acquire(inp->shift_dg); + } + + // Function defining the shift (and its context). + if (shift_func_op == 0) { + up->shift_func = ts_shift_dg_eval; + up->shift_dg_eval_ctx.shift_dg = up->shift_dg; + up->shift_dg_eval_ctx.shift_b = &up->shift_b; + up->shift_dg_eval_ctx.shear_grid = &up->shear_grid; + up->shift_dg_eval_ctx.shear_r = &up->shear_r; + up->shift_func_ctx = &up->shift_dg_eval_ctx; + } + else if (shift_func_op == 1) { + up->shift_func = inp->shift_func; + up->shift_func_ctx = inp->shift_func_ctx; + } + else { + fprintf(stderr, "Twist-shift function option not recognized. Exiting...\n"); + assert(false); + } + + // Find the donor cells for each target. Store the number of donors for each + // shear_dir idx (num_do) & the shift_dir idx of each donor (shift_dir_idx_do). + // i.e. allocates and assigns num_do and up->shift_dir_idx_do. + ts_find_donors(up); + + // Array of cummulative number of donors at given shear_dir cell. + const int num_do_cum_sz = up->grid.cells[up->shear_dir]+1; + int num_do_cum_ho[num_do_cum_sz]; + for (int i=0; ishear_r.lower[0]; ishear_r.upper[0]+1; i++) + num_do_cum_ho[i] = num_do_cum_ho[i-1] + up->num_do[i-up->shear_r.lower[0]]; + + if (!up->use_gpu) { + up->num_do_cum = gkyl_malloc(num_do_cum_sz * sizeof(int)); + memcpy(up->num_do_cum, num_do_cum_ho, num_do_cum_sz * sizeof(int)); + } +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + up->num_do_cum = gkyl_cu_malloc(num_do_cum_sz * sizeof(int)); + gkyl_cu_memcpy(up->num_do_cum, num_do_cum_ho, num_do_cum_sz * sizeof(int), GKYL_CU_MEMCPY_H2D); + } +#endif + + // Choose the kernels that do the subcell and full cell integrals + up->kernels = gkyl_malloc(sizeof(struct gkyl_twistshift_dg_kernels)); + gkyl_twistshift_dg_choose_kernels(inp->basis, inp->cdim, up->shift_poly_order, up->kernels); + + // The BC is applied as a set of matrix-matrix multiplications + // f_i = sum_{q}^{N_do(i)} A_q,i B_q,i + // where i indicates the shear_dir cell index, A_q is a + // num_basis x num_basis matrix containing the discretization + // of subcell integrals, B_q is a num_basis x (Ny * Nvpar * Nmu) matrix with + // the DG coefficients of f common to a given A_q matrix, and thus where f_i + // is a num_basis x (Ny*Nvpar*Nmu) matrix. + // + // Naming scheme: + // A_q: scimat (subscell integral matrices). + // B_q: fmat (distribution function matrices). + // A_q . B_q: mm_contr (contributions from mat-mat multiplication). + + // Calculate the entries in the matrices used to apply the BC. + up->scimat = ts_calc_mats(up); + + // Number of colums in fmat. + int fmat_num_col = 1; + for (int d=0; dbc_dir && d != up->shear_dir) + fmat_num_col *= up->local_bcdir_ext_r.upper[d] - up->local_bcdir_ext_r.lower[d] + 1; + } + + if (!up->use_gpu) { + up->fmat = gkyl_nmat_new(up->scimat->num, up->scimat->nr, fmat_num_col); + up->mm_contr = gkyl_nmat_new(up->scimat->num, up->scimat->nr, fmat_num_col); + } +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + up->fmat = gkyl_nmat_cu_dev_new(up->scimat->num, up->scimat->nr, fmat_num_col); + up->mm_contr = gkyl_nmat_cu_dev_new(up->scimat->num, up->scimat->nr, fmat_num_col); + } +#endif + + // Index translation from num-numcol plane index to linear index into the + // donor distribution function gkyl_array. + up->num_numcol_fidx_do = ts_calc_num_numcol_fidx_do(up); + + // Index translation from num-numcol plane index to linear index into the + // tar distribution function gkyl_array. + up->num_numcol_fidx_tar = ts_calc_num_numcol_fidx_tar(up); + + // Permutted ghost range, for indexing into the target field. + // Order: Shift direction, redundant directions, shear direction. + int lo4D[ndim-1], up4D[ndim-1]; + lo4D[0] = up->local_bcdir_ext_r.lower[up->shift_dir]; + up4D[0] = up->local_bcdir_ext_r.upper[up->shift_dir]; + int ic = 1; + for (int d=0; dbc_dir && d != up->shear_dir && d != up->shift_dir) { + lo4D[ic] = up->local_bcdir_ext_r.lower[d]; + up4D[ic] = up->local_bcdir_ext_r.upper[d]; + ic++; + } + } + lo4D[ndim-2] = up->local_bcdir_ext_r.lower[up->shear_dir]; + up4D[ndim-2] = up->local_bcdir_ext_r.upper[up->shear_dir]; + gkyl_range_init(&up->permutted_ghost_r, ndim-1, lo4D, up4D); + + // Create a ghost range, to clear it before adding contributions from TS BC. + if (inp->edge == GKYL_LOWER_EDGE) + gkyl_range_shorten_from_above(&up->ghost_r, &up->local_bcdir_ext_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); + else + gkyl_range_shorten_from_below(&up->ghost_r, &up->local_bcdir_ext_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); + + return up; +} + +void +gkyl_twistshift_dg_advance(struct gkyl_twistshift_dg *up, struct gkyl_array *fdo, struct gkyl_array *ftar) +{ + +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + gkyl_twistshift_dg_advance_cu(up, fdo, ftar); + return; + } +#endif + + // Assign the distribution matrices. + // This assumes that fdo->ncomp = fmat->nr. + // Recall: + // fmat->num = sum_i^Nx num_do(i) + // fmat->nr = num_basis = ncomp + // fmat->nc = Ny*Nvpar*Nmu + for (size_t i=0; ifmat->num * up->fmat->nr * up->fmat->nc; i++) { + + long nc_idx = i / up->fmat->nr; // num-num_col index: current num-num_col plane. + int row_idx = i % up->fmat->nr; // row index: current DG coeff. + + // This if-statement may only be needed in GPU kernel, not for CPUs. + if ((nc_idx < up->fmat->num * up->fmat->nc) && (row_idx < fdo->ncomp)) { + const double *fdo_c = (const double*) gkyl_array_cfetch(fdo, up->num_numcol_fidx_do[nc_idx]); + struct gkyl_mat mcurr = gkyl_nmat_get(up->fmat, nc_idx % up->fmat->num); + + gkyl_mat_set(&mcurr, row_idx, nc_idx/up->fmat->num, fdo_c[row_idx]); + } + } + + // Perform the mat-mat multiplications. + gkyl_nmat_mm(1.0, 0.0, GKYL_NO_TRANS, up->scimat, GKYL_NO_TRANS, up->fmat, up->mm_contr); + + // Clear the ghost range. + gkyl_array_clear_range(ftar, 0.0, &up->ghost_r); + + // Perform reduction over num_do contributions from mat-mat mults (mm_contr). + int num_cells_skin = (up->shear_r.upper[0]-up->shear_r.lower[0]+1) * up->fmat->nc; + for (size_t i=0; incomp * num_cells_skin; i++) { + + long linidx_tar = i / ftar->ncomp; + int row_idx = i % ftar->ncomp; + + // This if-statement may only be needed in GPU kernel, not for CPUs. + if ((linidx_tar < num_cells_skin) && (row_idx < ftar->ncomp)) { + double *ftar_c = (double*) gkyl_array_fetch(ftar, up->num_numcol_fidx_tar[linidx_tar]); + + int idx[GKYL_MAX_DIM] = {1}; + gkyl_sub_range_inv_idx(&up->permutted_ghost_r, linidx_tar, idx); + + int ac[GKYL_MAX_DIM] = {1}; + for (int d=2; dgrid.ndim-1; d++) + ac[d-2] = up->grid.cells[d+1]; + ac[up->permutted_ghost_r.ndim-2] = up->mm_contr->num; + + int start = 0; + for (int d=0; dpermutted_ghost_r.ndim-1; d++) + start = (start + (idx[d]-1)) * ac[d]; + + int shear_idx = idx[up->permutted_ghost_r.ndim-1]; + + int do_start = up->num_do_cum[shear_idx-1]; + int do_end = up->num_do_cum[shear_idx-1+1]; + for (int j=do_start; jmm_contr, linidx_mm_contr % up->mm_contr->num); + ftar_c[row_idx] += gkyl_mat_get(&mat, row_idx, linidx_mm_contr / up->mm_contr->num); + } + } + } +} + +struct gkyl_array* +gkyl_twistshift_dg_get_shift_objects(struct gkyl_twistshift_dg *up, struct gkyl_rect_grid *shear_grid, + struct gkyl_range *shear_r, struct gkyl_basis *shift_b) +{ + *shear_grid = up->shear_grid; + *shear_r = up->shear_r ; + *shift_b = up->shift_b ; + return gkyl_array_acquire(up->shift_dg); +}; + +void +gkyl_twistshift_dg_release(struct gkyl_twistshift_dg *up) { + // Release memory associated with this updater. + if (!up->use_gpu) { + gkyl_free(up->num_do_cum); + gkyl_free(up->num_numcol_fidx_do); + gkyl_free(up->num_numcol_fidx_tar); + } +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + gkyl_cu_free(up->num_do_cum); + gkyl_cu_free(up->num_numcol_fidx_do); + gkyl_cu_free(up->num_numcol_fidx_tar); + } +#endif + + gkyl_nmat_release(up->fmat); + gkyl_nmat_release(up->mm_contr); + + gkyl_nmat_release(up->scimat); + + gkyl_free(up->kernels); + + gkyl_array_release(up->shift_dg); + + gkyl_free(up->num_do); + gkyl_free(up->shift_dir_idx_do); + + gkyl_free(up); +} diff --git a/gyrokinetic/zero/bc_twistshift_cu.cu b/gyrokinetic/zero/twistshift_dg_cu.cu similarity index 89% rename from gyrokinetic/zero/bc_twistshift_cu.cu rename to gyrokinetic/zero/twistshift_dg_cu.cu index f4cbbecab7..a9987ffc92 100644 --- a/gyrokinetic/zero/bc_twistshift_cu.cu +++ b/gyrokinetic/zero/twistshift_dg_cu.cu @@ -8,8 +8,8 @@ extern "C" { #include #include #include -#include -#include +#include +#include } #include @@ -19,7 +19,7 @@ extern "C" { #define START_ID (threadIdx.x + blockIdx.x*blockDim.x) __global__ void -gkyl_bc_twistshift_set_distf_mats_cu_ker(const struct gkyl_array *fdo, const long *num_numcol_fidx_do, struct gkyl_nmat *fmat) +gkyl_twistshift_dg_set_distf_mats_cu_ker(const struct gkyl_array *fdo, const long *num_numcol_fidx_do, struct gkyl_nmat *fmat) { // Assign the distribution matrices. // This assumes that fdo->ncomp = fmat->nr. @@ -40,7 +40,7 @@ gkyl_bc_twistshift_set_distf_mats_cu_ker(const struct gkyl_array *fdo, const lon } __global__ void -gkyl_bc_twistshift_add_contr_cu_ker(struct gkyl_array *ftar, long *num_numcol_fidx_tar, int num_cells_skin, +gkyl_twistshift_dg_add_contr_cu_ker(struct gkyl_array *ftar, long *num_numcol_fidx_tar, int num_cells_skin, struct gkyl_nmat *mm_contr, int *num_do_cum, struct gkyl_range permutted_ghost_r, struct gkyl_rect_grid grid) { long linidx_tar = START_ID / ftar->ncomp; @@ -75,11 +75,11 @@ gkyl_bc_twistshift_add_contr_cu_ker(struct gkyl_array *ftar, long *num_numcol_fi } void -gkyl_bc_twistshift_advance_cu(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar) +gkyl_twistshift_dg_advance_cu(struct gkyl_twistshift_dg *up, struct gkyl_array *fdo, struct gkyl_array *ftar) { // Set the columns of the donor matrix with the donor distributions. int num_blocks_set = (up->fmat->num * up->fmat->nr * up->fmat->nc+GKYL_DEFAULT_NUM_THREADS-1)/GKYL_DEFAULT_NUM_THREADS; - gkyl_bc_twistshift_set_distf_mats_cu_ker<<>> + gkyl_twistshift_dg_set_distf_mats_cu_ker<<>> (fdo->on_dev, up->num_numcol_fidx_do, up->fmat->on_dev); // Perform the mat-mat multiplications. @@ -91,7 +91,7 @@ gkyl_bc_twistshift_advance_cu(struct gkyl_bc_twistshift *up, struct gkyl_array * // Add the contributions of mat-vec multiplications. int num_cells_skin = (up->shear_r.upper[0]-up->shear_r.lower[0]+1) * up->fmat->nc; int num_blocks_add = (ftar->ncomp * num_cells_skin+GKYL_DEFAULT_NUM_THREADS-1)/GKYL_DEFAULT_NUM_THREADS; - gkyl_bc_twistshift_add_contr_cu_ker<<>> + gkyl_twistshift_dg_add_contr_cu_ker<<>> (ftar->on_dev, up->num_numcol_fidx_tar, num_cells_skin, up->mm_contr->on_dev, up->num_do_cum, up->permutted_ghost_r, up->grid); } From 22dbc37b787c0aa81232c74e9d164a485ef24f2f Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Mon, 27 Jul 2026 12:02:42 -0400 Subject: [PATCH 08/30] typo in the filter_cutoff_wavelength attribute --- gyrokinetic/zero/gkyl_bc_twistshift_priv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h index 3dd134a9db..b39b6b62e1 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h @@ -21,7 +21,7 @@ struct gkyl_bc_twistshift { struct gkyl_dg_lowpass_filter *filter; // Optional post-shift filter along shear_dir. int filter_half_width; // Filter stencil half-width M in cells (0 = no filter). - int filter_cutoff_wavelength; // Filter cutoff wavelength. + double filter_cutoff_wavelength; // Filter cutoff wavelength. struct gkyl_range ghost_r; // Ghost plane the twist-shift fills. struct gkyl_array *filt_buff; // Buffer for the filter. void (*filter_func)(struct gkyl_dg_lowpass_filter *filt_up, struct gkyl_array *GKYL_RESTRICT finout, struct gkyl_array *GKYL_RESTRICT fbuff); From 79c778cdf33a769dc5bc5ff0a7a7bf31a3384c66 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Mon, 27 Jul 2026 13:40:14 -0400 Subject: [PATCH 09/30] restore periodicity outside of bc twistshift since it is done via comm per_sync in species. Remove related structure attributes. --- gyrokinetic/apps/gk_field_2x3x.c | 13 ++++++--- gyrokinetic/apps/gk_species.c | 14 ++------- gyrokinetic/zero/bc_twistshift.c | 34 +++++++++++----------- gyrokinetic/zero/gkyl_bc_twistshift.h | 4 +-- gyrokinetic/zero/gkyl_bc_twistshift_priv.h | 3 -- 5 files changed, 29 insertions(+), 39 deletions(-) diff --git a/gyrokinetic/apps/gk_field_2x3x.c b/gyrokinetic/apps/gk_field_2x3x.c index 2ee15a67ad..c90d187c62 100644 --- a/gyrokinetic/apps/gk_field_2x3x.c +++ b/gyrokinetic/apps/gk_field_2x3x.c @@ -60,7 +60,11 @@ gk_field_fem_projection_par_phi_ts_3x(gkyl_gyrokinetic_app *app, struct gk_field // Gather the DG array into a global (in z) array. gkyl_comm_array_allgather(app->comm, &app->local, &app->global, arr_dg, field->rho_c_global_dg); - // Apply TS BC in the lower parallel boundary. + // Apply TS BC in the core lower parallel boundary, and + // fill core upper parallel boundary ghost with skin boundary value. + int par_dir = app->cdim-1; // Parallel direction index. + gkyl_array_copy_range_to_range(field->rho_c_global_dg, field->rho_c_global_dg, + &app->global_lower_ghost[par_dir], &app->global_upper_skin[par_dir]); gkyl_bc_twistshift_advance(field->bc_ts_lo, field->rho_c_global_dg, field->rho_c_global_dg); // Fill upper parallel boundary ghost with skin boundary value. gkyl_bc_basic_gyrokinetic_advance(field->gfss_bc_op_core_up, field->bc_buffer, field->rho_c_global_dg); @@ -124,7 +128,10 @@ gk_field_fem_projection_par_phi_iwl_3x(gkyl_gyrokinetic_app *app, struct gk_fiel // Gather the DG array into a global (in z) array. gkyl_comm_array_allgather(app->comm, &app->local, &app->global, arr_dg, field->rho_c_global_dg); - // Apply TS BC in the core lower parallel boundary. + // Apply TS BC in the core lower parallel boundary, and + // fill core upper parallel boundary ghost with skin boundary value. + gkyl_array_copy_range_to_range(field->rho_c_global_dg, field->rho_c_global_dg, + &app->global_lower_ghost_par_core, &app->global_upper_skin_par_core); gkyl_bc_twistshift_advance(field->bc_ts_lo, field->rho_c_global_dg, field->rho_c_global_dg); gkyl_bc_basic_gyrokinetic_advance(field->gfss_bc_op_core_up, field->bc_buffer, field->rho_c_global_dg); @@ -363,8 +370,6 @@ gk_field_2x3x_add_IWL_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field .basis = app->basis, .grid = app->grid, .use_gpu = app->use_gpu, - .periodic_in_r = &app->global_lower_ghost_par_core, - .periodic_out_r = &app->global_upper_skin_par_core }; if (app->gk_geom->geometry_id == GKYL_GEOMETRY_TOKAMAK) T_LU_lo.shift_dg = app->delta_ts_x_lo; diff --git a/gyrokinetic/apps/gk_species.c b/gyrokinetic/apps/gk_species.c index 2fc13cf1ae..627aa8b05d 100644 --- a/gyrokinetic/apps/gk_species.c +++ b/gyrokinetic/apps/gk_species.c @@ -196,14 +196,6 @@ gk_species_rhs_implicit_static(gkyl_gyrokinetic_app *app, struct gk_species *spe return app->cfl/omega_cfl; } -static void -gk_species_apply_ts_bc_interp(gkyl_gyrokinetic_app *app, const struct gk_species *gks, - enum gkyl_edge_loc edge, struct gkyl_array *fin, struct gkyl_array *fout) -{ - // Apply the twist-shift BC at the given edge. - gkyl_bc_twistshift_advance(edge == GKYL_LOWER_EDGE? gks->bc_ts_lo : gks->bc_ts_up, fin, fout); -} - static void gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species *species, struct gkyl_array *f) { @@ -222,8 +214,7 @@ gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species * app->field->phi_wall_lo, f, &app->local); break; case GKYL_BC_GK_SPECIES_TWISTSHIFT: -// gkyl_bc_twistshift_advance(species->bc_ts_lo, f, f); - gk_species_apply_ts_bc_interp(app, species, GKYL_LOWER_EDGE, f, f); + gkyl_bc_twistshift_advance(species->bc_ts_lo, f, f); break; case GKYL_BC_GK_SPECIES_COPY: case GKYL_BC_GK_SPECIES_REFLECT: @@ -245,8 +236,7 @@ gk_species_apply_bc_dynamic(gkyl_gyrokinetic_app *app, const struct gk_species * app->field->phi_wall_up, f, &app->local); break; case GKYL_BC_GK_SPECIES_TWISTSHIFT: -// gkyl_bc_twistshift_advance(species->bc_ts_up, f, f); - gk_species_apply_ts_bc_interp(app, species, GKYL_UPPER_EDGE, f, f); + gkyl_bc_twistshift_advance(species->bc_ts_up, f, f); break; case GKYL_BC_GK_SPECIES_COPY: case GKYL_BC_GK_SPECIES_REFLECT: diff --git a/gyrokinetic/zero/bc_twistshift.c b/gyrokinetic/zero/bc_twistshift.c index 2b9f85de83..3764d03593 100644 --- a/gyrokinetic/zero/bc_twistshift.c +++ b/gyrokinetic/zero/bc_twistshift.c @@ -55,16 +55,18 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) struct gkyl_bc_twistshift *up = gkyl_malloc(sizeof(*up)); up->use_gpu = inp->use_gpu; - up->bc_dir = inp->bc_dir; up->filter_half_width = inp->filter_half_width; up->filter_cutoff_wavelength = inp->filter_cutoff_wavelength; up->upsample_factor = inp->upsample_factor > 1 ? inp->upsample_factor : 1; - up->periodic_in_r = inp->periodic_in_r; - up->periodic_out_r = inp->periodic_out_r; - const int ndim = inp->bcdir_ext_update_r.ndim; + // Ensure that we use filter if we upsample. + if (up->upsample_factor > 1) { + assert(up->filter_half_width > 1); + assert(up->filter_cutoff_wavelength > 0.0); + } // Grid and update range for the twist-shift. + const int ndim = inp->bcdir_ext_update_r.ndim; int fine_cells[GKYL_MAX_DIM]; for (int d=0; dgrid.cells[d]; fine_cells[inp->shear_dir] *= up->upsample_factor; @@ -78,15 +80,15 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) flo[d] = fine_local.lower[d]; fup[d] = fine_local.upper[d]; } - flo[up->bc_dir] = fine_ext.lower[up->bc_dir]; - fup[up->bc_dir] = fine_ext.upper[up->bc_dir]; + flo[inp->bc_dir] = fine_ext.lower[inp->bc_dir]; + fup[inp->bc_dir] = fine_ext.upper[inp->bc_dir]; gkyl_sub_range_init(&up->ts_update_r, &fine_ext, flo, fup); up->ffine = gkyl_array_new(GKYL_DOUBLE, inp->basis.num_basis, fine_ext.volume); // The pure twist-shift updater. struct gkyl_twistshift_dg_inp tsinp = { - .bc_dir = up->bc_dir, + .bc_dir = inp->bc_dir, .shift_dir = inp->shift_dir, .shear_dir = inp->shear_dir, .edge = inp->edge, @@ -105,10 +107,11 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) // Ghost plane the twist-shift fills, on the ts grid. if (inp->edge == GKYL_LOWER_EDGE) - gkyl_range_shorten_from_above(&up->ghost_r, &up->ts_update_r, up->bc_dir, inp->num_ghost[up->bc_dir]); + gkyl_range_shorten_from_above(&up->ghost_r, &up->ts_update_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); else - gkyl_range_shorten_from_below(&up->ghost_r, &up->ts_update_r, up->bc_dir, inp->num_ghost[up->bc_dir]); + gkyl_range_shorten_from_below(&up->ghost_r, &up->ts_update_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); + // Optional low-pass filter along shear_dir. up->filter = NULL; up->filt_buff = NULL; up->filter_func = bc_twistshift_filter_disabled; @@ -116,18 +119,17 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) up->coarsen = NULL; up->refine_func = bc_twistshift_refine_disabled; up->coarsen_func = bc_twistshift_coarsen_disabled; - // Optional low-pass filter + refine/coarsen along shear_dir. if (up->filter_half_width > 0) { up->filt_buff = gkyl_array_new(GKYL_DOUBLE, inp->basis.num_basis, up->ffine->size); up->filter = gkyl_dg_lowpass_filter_new(inp->shear_dir, up->filter_half_width, - up->filter_cutoff_wavelength, &inp->basis, &up->ts_grid, &up->ghost_r, inp->use_gpu); - up->filter_func = bc_twistshift_filter_enabled; - // Refine/coarsen interpolators. + up->filter_cutoff_wavelength, &inp->basis, &up->ts_grid, &up->ghost_r, inp->use_gpu); + up->filter_func = bc_twistshift_filter_enabled; + // Optional refine/coarsen interpolators along shear_dir. if (up->upsample_factor > 1) { if (inp->edge == GKYL_LOWER_EDGE) - gkyl_range_shorten_from_above(&up->coarse_ghost_r, &inp->bcdir_ext_update_r, up->bc_dir, inp->num_ghost[up->bc_dir]); + gkyl_range_shorten_from_above(&up->coarse_ghost_r, &inp->bcdir_ext_update_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); else - gkyl_range_shorten_from_below(&up->coarse_ghost_r, &inp->bcdir_ext_update_r, up->bc_dir, inp->num_ghost[up->bc_dir]); + gkyl_range_shorten_from_below(&up->coarse_ghost_r, &inp->bcdir_ext_update_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); up->refine = gkyl_dg_interpolate_new(inp->cdim, &inp->basis, &inp->grid, &up->ts_grid, &up->coarse_ghost_r, &up->ghost_r, inp->num_ghost, inp->use_gpu); up->coarsen = gkyl_dg_interpolate_new(inp->cdim, &inp->basis, &up->ts_grid, &inp->grid, @@ -143,8 +145,6 @@ gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp) void gkyl_bc_twistshift_advance(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar) { - // Apply periodicity. - gkyl_array_copy_range_to_range(fdo, fdo, up->periodic_in_r, up->periodic_out_r); // Refine the data to the fine grid. up->refine_func(up->refine, fdo, up->ffine); // Apply twist-shift. diff --git a/gyrokinetic/zero/gkyl_bc_twistshift.h b/gyrokinetic/zero/gkyl_bc_twistshift.h index 72d485c19b..75bf0f05d0 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift.h @@ -12,8 +12,6 @@ typedef struct gkyl_bc_twistshift gkyl_bc_twistshift; struct gkyl_bc_twistshift_inp { int bc_dir; // Direction in which to apply this BC. - struct gkyl_range *periodic_out_r; // Range of the periodic donor (output) field. - struct gkyl_range *periodic_in_r; // Range of the periodic donor (input) field. int shift_dir; // Direction of the shift. int shear_dir; // Direction in which the shift varies (shear). enum gkyl_edge_loc edge; // Edge to apply this BC at (lower/upper). @@ -49,7 +47,7 @@ struct gkyl_bc_twistshift_inp { struct gkyl_bc_twistshift* gkyl_bc_twistshift_new(const struct gkyl_bc_twistshift_inp *inp); /** - * Apply the twist-shift periodic BC. Can be used in-place. + * Apply the twist-shift periodic BC. Expect periodicity to be applied beforehand. * * @param up Twist-shift BC updater object. * @param fdo Donor field. diff --git a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h index b39b6b62e1..e0b39318fc 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h @@ -15,9 +15,6 @@ struct gkyl_bc_twistshift { bool use_gpu; // Whether to apply the BC on the GPU. struct gkyl_twistshift_dg *ts; // Pure twist-shift updater. - int bc_dir; // Direction along which we treat the BC. - struct gkyl_range *periodic_out_r; // Range of the periodic donor (output) field. - struct gkyl_range *periodic_in_r; // Range of the periodic donor (input) field. struct gkyl_dg_lowpass_filter *filter; // Optional post-shift filter along shear_dir. int filter_half_width; // Filter stencil half-width M in cells (0 = no filter). From 01b43fd4d6709931507d989a141f50bebb286cd4 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Wed, 29 Jul 2026 10:39:14 -0400 Subject: [PATCH 10/30] the stencil of the filter is now reflected at the boundary to conserve sum(h_k) = 1 which conserves the first moment. No need to renormalize the stencil anymore. We also add a new unit test for the twistshift bc updater where we verify that the filter filters, the upsampler upsamples and the trivial case returns trivially. --- core/unit/ctest_dg_lowpass_filter.c | 57 ++- core/zero/dg_lowpass_filter.c | 15 +- core/zero/gkyl_dg_lowpass_filter.h | 3 +- core/zero/gkyl_dg_lowpass_filter_priv.h | 11 + gyrokinetic/apps/gkyl_gyrokinetic.h | 6 +- gyrokinetic/unit/ctest_bc_twistshift.c | 496 +++++++++++++++++++++ gyrokinetic/zero/bc_twistshift.c | 267 ++++++----- gyrokinetic/zero/gkyl_bc_twistshift.h | 32 +- gyrokinetic/zero/gkyl_bc_twistshift_priv.h | 26 +- gyrokinetic/zero/twistshift_dg.c | 3 + 10 files changed, 718 insertions(+), 198 deletions(-) create mode 100644 gyrokinetic/unit/ctest_bc_twistshift.c diff --git a/core/unit/ctest_dg_lowpass_filter.c b/core/unit/ctest_dg_lowpass_filter.c index a67a3cc72d..479b38cb0d 100644 --- a/core/unit/ctest_dg_lowpass_filter.c +++ b/core/unit/ctest_dg_lowpass_filter.c @@ -62,6 +62,12 @@ void eval_gauss_1x(double t, const double *xn, double *fout, void *ctx) fout[0] = exp(-pow((xn[0]-0.5)/0.04, 2)); } +void eval_gauss_edge_1x(double t, const double *xn, double *fout, void *ctx) +{ + // Peaks at the lower boundary, so the stencil there is heavily reflected. + fout[0] = exp(-pow(xn[0]/0.04, 2)); +} + void eval_mode_3x(double t, const double *xn, double *fout, void *ctx) { // Separable function: an x-Nyquist mode (for a 32 cell grid on [0,1]) @@ -96,7 +102,7 @@ test_1x(void) struct gkyl_array *fout = mkarr(basis.num_basis, local_ext.volume); // a) A constant field is preserved exactly everywhere, including at - // the boundaries where the stencil is truncated and renormalized. + // the boundaries where the stencil is reflected. gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, poly_order+1, 1, eval_const_1x, NULL); gkyl_proj_on_basis_advance(proj, 0.0, &local, fin); @@ -188,10 +194,7 @@ test_1x(void) static void test_1x_conservation(void) { - // The total integral is conserved where full stencils apply (the - // kernel weights sum to 1). Renormalized (truncated) stencils alter - // column sums up to 2M cells from the boundary, so use a field with - // negligible amplitude in that band. + // The total integral is conserved everywhere: the kernel weights sum to 1 (reflected stencil at the boundaries). int poly_order = 1; int cells[] = {64}; double lower[] = {0.0}, upper[] = {1.0}; @@ -205,31 +208,39 @@ test_1x_conservation(void) struct gkyl_range local, local_ext; gkyl_create_grid_ranges(&grid, nghost, &local_ext, &local); - struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, 8, - grid.dx[0]/0.3, &basis, &grid, &local, false); - struct gkyl_array *fin = mkarr(basis.num_basis, local_ext.volume); struct gkyl_array *fout = mkarr(basis.num_basis, local_ext.volume); - gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, - poly_order+1, 1, eval_gauss_1x, NULL); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin); - gkyl_proj_on_basis_release(proj); + // A ramp peaks at one boundary, a boundary-hugging gaussian at the other. + evalf_t evals[] = {eval_linear_1x, eval_gauss_edge_1x, eval_gauss_1x}; + int half_widths[] = {8, 8, 80}; // The last stencil is wider than the grid. - gkyl_dg_lowpass_filter_advance(lpf, fin, fout); + for (int q=0; q<3; q++) { + struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, half_widths[q], + grid.dx[0]/0.3, &basis, &grid, &local, false); - double tot_in = 0.0, tot_out = 0.0; - struct gkyl_range_iter iter; - gkyl_range_iter_init(&iter, &local); - while (gkyl_range_iter_next(&iter)) { - long linidx = gkyl_range_idx(&local, iter.idx); - tot_in += ((const double *) gkyl_array_cfetch(fin, linidx))[0]; - tot_out += ((const double *) gkyl_array_cfetch(fout, linidx))[0]; + gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, + poly_order+1, 1, evals[q], NULL); + gkyl_proj_on_basis_advance(proj, 0.0, &local, fin); + gkyl_proj_on_basis_release(proj); + + gkyl_dg_lowpass_filter_advance(lpf, fin, fout); + + double tot_in = 0.0, tot_out = 0.0; + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &local); + while (gkyl_range_iter_next(&iter)) { + long linidx = gkyl_range_idx(&local, iter.idx); + tot_in += ((const double *) gkyl_array_cfetch(fin, linidx))[0]; + tot_out += ((const double *) gkyl_array_cfetch(fout, linidx))[0]; + } + TEST_CHECK( fabs(tot_out-tot_in) < 1e-12*fabs(tot_in) ); + TEST_MSG("case %d (M=%d): total in %.13e | out %.13e | rel change %.3e", + q, half_widths[q], tot_in, tot_out, fabs(tot_out-tot_in)/fabs(tot_in)); + + gkyl_dg_lowpass_filter_release(lpf); } - TEST_CHECK( gkyl_compare(tot_in, tot_out, 1e-12) ); - TEST_MSG("total in: %.13e | total out: %.13e", tot_in, tot_out); - gkyl_dg_lowpass_filter_release(lpf); gkyl_array_release(fin); gkyl_array_release(fout); } diff --git a/core/zero/dg_lowpass_filter.c b/core/zero/dg_lowpass_filter.c index 9ac0c1efe4..491c29cbab 100644 --- a/core/zero/dg_lowpass_filter.c +++ b/core/zero/dg_lowpass_filter.c @@ -56,19 +56,15 @@ gkyl_dg_lowpass_filter_advance(gkyl_dg_lowpass_filter *up, long linidx_tar = gkyl_range_idx(&up->range, iter.idx); double *ftar_c = gkyl_array_fetch(ftar, linidx_tar); - // Truncate the stencil at the boundaries of the range. - int koff_lo = GKYL_MAX2(-M, up->range.lower[dir]-iter.idx[dir]); - int koff_up = GKYL_MIN2( M, up->range.upper[dir]-iter.idx[dir]); - gkyl_copy_int_arr(up->range.ndim, iter.idx, idx_do); for (int c=0; crange.lower[dir], + up->range.upper[dir]); long linidx_do = gkyl_range_idx(&up->range, idx_do); const double *fdo_c = gkyl_array_cfetch(fdo, linidx_do); @@ -76,12 +72,7 @@ gkyl_dg_lowpass_filter_advance(gkyl_dg_lowpass_filter *up, double w = up->weights[k+M]; for (int c=0; c up) { + if (idx < lo) idx = 2*lo - 1 - idx; + if (idx > up) idx = 2*up + 1 - idx; + } + return idx; +} + static void dg_lpf_calc_weights(int half_width, double fc, double *weights) { diff --git a/gyrokinetic/apps/gkyl_gyrokinetic.h b/gyrokinetic/apps/gkyl_gyrokinetic.h index d6a19df9a3..cd23b0c6fe 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic.h @@ -251,9 +251,9 @@ struct gkyl_gyrokinetic_geometry { void *parallel_upper_bc_shift_ctx; // Context for parallel_upper_bc_shift_func. // Twist-shift anti-aliasing filter. - int ts_upsample_factor; // Supersampling factor along x and y. - int ts_filter_half_width; // Filter stencil half-width in fine cells. - double ts_filter_cutoff_wavelength; // Filter cutoff wavelength. + int ts_upsample_factor; // Supersampling factor along the shear direction. + int ts_filter_half_width; // Filter stencil half-width in fine cells (0 = off, must be > 1). + double ts_filter_cutoff_wavelength; // Filter cutoff wavelength (use 2*dx to cut at the coarse Nyquist). struct gkyl_efit_inp efit_info; // Context with RZ data such as efit file for a tokamak or mirror. struct gkyl_tok_geo_grid_inp tok_grid_info; // Context for tokamak geometry with computational domain info. diff --git a/gyrokinetic/unit/ctest_bc_twistshift.c b/gyrokinetic/unit/ctest_bc_twistshift.c new file mode 100644 index 0000000000..dc75e02776 --- /dev/null +++ b/gyrokinetic/unit/ctest_bc_twistshift.c @@ -0,0 +1,496 @@ +// Test the bc_twistshift orchestrator: the twist-shift BC on its own, and the +// supersample -> twist-shift -> low-pass filter -> restrict pipeline used to +// de-alias the shift along the shear direction. The sub-cell integrals the +// shift is built from are tested separately, in ctest_twistshift_dg. +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static const double ts_lower[] = {-2.0, -1.5, -3.0}; +static const double ts_upper[] = { 2.0, 1.5, 3.0}; +static const int ts_cells[] = {8, 8, 4}; +static const int ts_ndim = 3, ts_cdim = 3, ts_bc_dir = 2; + +struct ts_ctx { + double offset; // Shift at x=0. + double shear; // Rate at which the shift varies with x (0 = no shear). +}; + +static void +shift_func(double t, const double *xn, double *fout, void *ctx) +{ + struct ts_ctx *tctx = ctx; + fout[0] = tctx->offset + tctx->shear*xn[0]; +} + +static void +init_donor(double t, const double *xn, double *fout, void *ctx) +{ + double x = xn[0], y = xn[1]; + fout[0] = exp(-y*y/(2.0*0.3*0.3))*(1.0 + 0.5*cos(M_PI*x)); +} + +// Everything needed to build and apply the BC on the test grid. +struct ts_setup { + struct gkyl_rect_grid grid; + struct gkyl_basis basis; + struct gkyl_range local, local_ext; + struct gkyl_range update_r; // Local range, extended in bc_dir. + struct gkyl_range ghost_r; // Plane the BC writes. + struct gkyl_range skin_r; // Plane periodicity copies from. + int ghost[GKYL_MAX_DIM]; +}; + +static void +ts_setup_init(struct ts_setup *s, enum gkyl_edge_loc edge) +{ + gkyl_rect_grid_init(&s->grid, ts_ndim, ts_lower, ts_upper, ts_cells); + gkyl_cart_modal_serendip(&s->basis, ts_ndim, 1); + + for (int d=0; dghost[d] = 1; + gkyl_create_grid_ranges(&s->grid, s->ghost, &s->local_ext, &s->local); + + int lo[GKYL_MAX_DIM], up[GKYL_MAX_DIM]; + for (int d=0; dlocal.lower[d]; + up[d] = s->local.upper[d]; + } + lo[ts_bc_dir] = s->local_ext.lower[ts_bc_dir]; + up[ts_bc_dir] = s->local_ext.upper[ts_bc_dir]; + gkyl_sub_range_init(&s->update_r, &s->local_ext, lo, up); + + // The BC reads and writes the same ghost plane; periodicity fills it from + // the skin cell at the other end. + int slo[GKYL_MAX_DIM], sup[GKYL_MAX_DIM]; + for (int d=0; dlocal.lower[d]; + sup[d] = s->local.upper[d]; + } + if (edge == GKYL_LOWER_EDGE) { + gkyl_range_shorten_from_above(&s->ghost_r, &s->update_r, ts_bc_dir, s->ghost[ts_bc_dir]); + slo[ts_bc_dir] = sup[ts_bc_dir] = s->local.upper[ts_bc_dir]; + } + else { + gkyl_range_shorten_from_below(&s->ghost_r, &s->update_r, ts_bc_dir, s->ghost[ts_bc_dir]); + slo[ts_bc_dir] = sup[ts_bc_dir] = s->local.lower[ts_bc_dir]; + } + gkyl_sub_range_init(&s->skin_r, &s->local_ext, slo, sup); +} + +// Project the donor and apply periodicity along bc_dir. +static struct gkyl_array* +ts_donor_new(const struct ts_setup *s) +{ + struct gkyl_array *f = gkyl_array_new(GKYL_DOUBLE, s->basis.num_basis, s->local_ext.volume); + gkyl_array_clear(f, 0.0); + + gkyl_proj_on_basis *proj = gkyl_proj_on_basis_inew(&(struct gkyl_proj_on_basis_inp) { + .grid = &s->grid, .basis = &s->basis, .num_ret_vals = 1, .eval = init_donor, .ctx = NULL }); + gkyl_proj_on_basis_advance(proj, 0.0, &s->local, f); + gkyl_proj_on_basis_release(proj); + + gkyl_array_copy_range_to_range(f, f, &s->ghost_r, &s->skin_r); + return f; +} + +// The 1D DG shift on the shear cells of the update range, as the app builds it. +static struct gkyl_array* +ts_shift_dg_new(const struct ts_setup *s, struct ts_ctx *tctx) +{ + struct gkyl_rect_grid xgrid; + gkyl_rect_grid_init(&xgrid, 1, &ts_lower[0], &ts_upper[0], &ts_cells[0]); + struct gkyl_basis xbasis; + gkyl_cart_modal_serendip(&xbasis, 1, s->basis.poly_order); + + struct gkyl_range xrng; + gkyl_range_init(&xrng, 1, (int[]) {s->update_r.lower[0]}, (int[]) {s->update_r.upper[0]}); + + struct gkyl_array *shift_dg = gkyl_array_new(GKYL_DOUBLE, xbasis.num_basis, xrng.volume); + gkyl_eval_on_nodes *ev = gkyl_eval_on_nodes_new(&xgrid, &xbasis, 1, shift_func, tctx); + gkyl_eval_on_nodes_advance(ev, 0.0, &xrng, shift_dg); + gkyl_eval_on_nodes_release(ev); + return shift_dg; +} + +// Apply the BC to a fresh donor field and return it. Pass shift_dg to exercise +// the discretized-shift input instead of the shift function. +static struct gkyl_array* +ts_run(const struct ts_setup *s, enum gkyl_edge_loc edge, int upsample, int half_width, + double cutoff, struct gkyl_array *shift_dg, struct ts_ctx *tctx) +{ + struct gkyl_array *f = ts_donor_new(s); + + struct gkyl_bc_twistshift_inp inp = { + .bc_dir = ts_bc_dir, + .shift_dir = 1, + .shear_dir = 0, + .edge = edge, + .cdim = ts_cdim, + .bcdir_ext_update_r = &s->update_r, + .num_ghost = s->ghost, + .basis = &s->basis, + .grid = &s->grid, + .use_gpu = false, + .upsample_factor = upsample, + .filter_half_width = half_width, + .filter_cutoff_wavelength = cutoff, + }; + if (shift_dg) + inp.shift_dg = shift_dg; + else { + inp.shift_func = shift_func; + inp.shift_func_ctx = tctx; + } + + struct gkyl_bc_twistshift *up = gkyl_bc_twistshift_inew(&inp); + gkyl_bc_twistshift_advance(up, f, f); + gkyl_bc_twistshift_release(up); + return f; +} + +// Largest difference between two fields over a range. +static double +ts_max_diff(const struct gkyl_array *fa, const struct gkyl_array *fb, + const struct gkyl_range *rng, int num_basis) +{ + double maxd = 0.0; + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, rng); + while (gkyl_range_iter_next(&iter)) { + long linidx = gkyl_range_idx(rng, iter.idx); + const double *a = gkyl_array_cfetch(fa, linidx); + const double *b = gkyl_array_cfetch(fb, linidx); + for (int k=0; ksize; i++) { + const double *a = gkyl_array_cfetch(fa, i); + const double *b = gkyl_array_cfetch(fb, i); + for (int k=0; kbasis.num_basis; k++) + maxd = GKYL_MAX2(maxd, fabs(a[k]-b[k])); + } + return maxd; +} + +// Integral of the field over the plane the BC fills. +static double +ts_ghost_sum(const struct ts_setup *s, const struct gkyl_array *f) +{ + double tot = 0.0; + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &s->ghost_r); + while (gkyl_range_iter_next(&iter)) + tot += ((const double *) gkyl_array_cfetch(f, gkyl_range_idx(&s->ghost_r, iter.idx)))[0]; + return tot; +} + +void +test_plain_matches_twistshift_dg(void) +{ + // With no anti-aliasing the BC must be exactly the bare twist-shift. + struct ts_ctx tctx = { .offset = 0.75, .shear = 0.25 }; + for (int e=0; e<2; e++) { + enum gkyl_edge_loc edge = e == 0? GKYL_LOWER_EDGE : GKYL_UPPER_EDGE; + struct ts_setup s; + ts_setup_init(&s, edge); + + struct gkyl_array *f_bc = ts_run(&s, edge, 1, 0, 0.0, NULL, &tctx); + + struct gkyl_array *f_ref = ts_donor_new(&s); + struct gkyl_twistshift_dg_inp tsinp = { + .bc_dir = ts_bc_dir, .shift_dir = 1, .shear_dir = 0, .edge = edge, + .cdim = ts_cdim, .bcdir_ext_update_r = s.update_r, .num_ghost = s.ghost, + .basis = s.basis, .grid = s.grid, + .shift_func = shift_func, .shift_func_ctx = &tctx, .use_gpu = false, + }; + struct gkyl_twistshift_dg *ts = gkyl_twistshift_dg_new(&tsinp); + gkyl_twistshift_dg_advance(ts, f_ref, f_ref); + gkyl_twistshift_dg_release(ts); + + TEST_CHECK( ts_max_diff_all(&s, f_bc, f_ref) == 0.0 ); + TEST_MSG("edge %d: bc_twistshift differs from twistshift_dg by %.3e", e, + ts_max_diff_all(&s, f_bc, f_ref)); + + gkyl_array_release(f_bc); + gkyl_array_release(f_ref); + } +} + +void +test_only_ghost_plane_written(void) +{ + // Nothing but the ghost plane the BC fills may change, in any configuration. + struct ts_ctx tctx = { .offset = 0.75, .shear = 0.25 }; + double dx = (ts_upper[0]-ts_lower[0])/ts_cells[0]; + int upsample[] = {1, 1, 2, 4}; + int half_width[] = {0, 4, 4, 4}; + double cutoff[] = {0.0, 4.0*dx, 2.0*dx, 2.0*dx}; + + for (int e=0; e<2; e++) { + enum gkyl_edge_loc edge = e == 0? GKYL_LOWER_EDGE : GKYL_UPPER_EDGE; + struct ts_setup s; + ts_setup_init(&s, edge); + + struct gkyl_array *f_pre = ts_donor_new(&s); + + for (int c=0; c<4; c++) { + struct gkyl_array *f = ts_run(&s, edge, upsample[c], half_width[c], cutoff[c], NULL, &tctx); + + // Restore the ghost plane so any remaining difference is a stray write. + gkyl_array_copy_range(f, f_pre, &s.ghost_r); + double maxd = ts_max_diff_all(&s, f, f_pre); + TEST_CHECK( maxd == 0.0 ); + TEST_MSG("edge %d, config %d (upsample %d, M %d): wrote %.3e outside the ghost plane", + e, c, upsample[c], half_width[c], maxd); + + gkyl_array_release(f); + } + gkyl_array_release(f_pre); + } +} + +void +test_identity_filter_matches_plain(void) +{ + // A cutoff at the Nyquist wavelength makes the kernel the identity, so + // filtering must leave the plain twist-shift result alone. + struct ts_ctx tctx = { .offset = 0.75, .shear = 0.25 }; + double dx = (ts_upper[0]-ts_lower[0])/ts_cells[0]; + + struct ts_setup s; + ts_setup_init(&s, GKYL_LOWER_EDGE); + + struct gkyl_array *f_plain = ts_run(&s, GKYL_LOWER_EDGE, 1, 0, 0.0, NULL, &tctx); + struct gkyl_array *f_filt = ts_run(&s, GKYL_LOWER_EDGE, 1, 4, 2.0*dx, NULL, &tctx); + + double maxd = ts_max_diff(f_plain, f_filt, &s.ghost_r, s.basis.num_basis); + TEST_CHECK( maxd < 1.0e-13 ); + TEST_MSG("identity filter changed the result by %.3e", maxd); + + gkyl_array_release(f_plain); + gkyl_array_release(f_filt); +} + +void +test_upsample_no_shear_matches_plain(void) +{ + // With a uniform shift the twist-shift creates no structure along x, so the + // supersampled field stays an exact refinement of the coarse one and the + // refine/identity-filter/restrict round trip must be lossless. + struct ts_ctx tctx = { .offset = 0.75, .shear = 0.0 }; + double dx = (ts_upper[0]-ts_lower[0])/ts_cells[0]; + + for (int upsample=2; upsample<=4; upsample*=2) { + struct ts_setup s; + ts_setup_init(&s, GKYL_LOWER_EDGE); + + struct gkyl_array *f_plain = ts_run(&s, GKYL_LOWER_EDGE, 1, 0, 0.0, NULL, &tctx); + struct gkyl_array *f_up = ts_run(&s, GKYL_LOWER_EDGE, upsample, 4, 2.0*dx/upsample, NULL, &tctx); + + double maxd = ts_max_diff(f_plain, f_up, &s.ghost_r, s.basis.num_basis); + TEST_CHECK( maxd < 1.0e-12 ); + TEST_MSG("upsample %d round trip changed the result by %.3e", upsample, maxd); + + gkyl_array_release(f_plain); + gkyl_array_release(f_up); + } +} + +void +test_shift_dg_matches_shift_func(void) +{ + // A discretized shift given on the field's own grid must give the same answer + // as the shift function, including when the shift has to be refined. + struct ts_ctx tctx = { .offset = 0.75, .shear = 0.25 }; + double dx = (ts_upper[0]-ts_lower[0])/ts_cells[0]; + + for (int upsample=1; upsample<=2; upsample++) { + struct ts_setup s; + ts_setup_init(&s, GKYL_LOWER_EDGE); + struct gkyl_array *shift_dg = ts_shift_dg_new(&s, &tctx); + + int half_width = upsample > 1? 4 : 0; + double cutoff = upsample > 1? 2.0*dx : 0.0; + + struct gkyl_array *f_func = ts_run(&s, GKYL_LOWER_EDGE, upsample, half_width, cutoff, NULL, &tctx); + struct gkyl_array *f_dg = ts_run(&s, GKYL_LOWER_EDGE, upsample, half_width, cutoff, shift_dg, &tctx); + + double maxd = ts_max_diff(f_func, f_dg, &s.ghost_r, s.basis.num_basis); + TEST_CHECK( maxd < 1.0e-12 ); + TEST_MSG("upsample %d: shift_dg differs from shift_func by %.3e", upsample, maxd); + + gkyl_array_release(f_func); + gkyl_array_release(f_dg); + gkyl_array_release(shift_dg); + } +} + +void +test_dealiasing_smooths_shear_direction(void) +{ + // Check that the anti-aliasing filter actually smooths the field along the shear direction. + struct ts_ctx tctx = { .offset = 0.75, .shear = 2.9 }; + double dx = (ts_upper[0]-ts_lower[0])/ts_cells[0]; + + struct ts_setup s; + ts_setup_init(&s, GKYL_LOWER_EDGE); + + struct gkyl_array *f_plain = ts_run(&s, GKYL_LOWER_EDGE, 1, 0, 0.0, NULL, &tctx); + struct gkyl_array *f_deal = ts_run(&s, GKYL_LOWER_EDGE, 4, 4, 2.0*dx, NULL, &tctx); + + // Total variation along x of the cell averages, summed over the ghost plane. + double tv[2] = {0.0, 0.0}; + struct gkyl_array *fs[] = {f_plain, f_deal}; + for (int q=0; q<2; q++) { + for (int j=s.ghost_r.lower[1]; j<=s.ghost_r.upper[1]; j++) { + for (int i=s.ghost_r.lower[0]; i %.13e, rel change %.3e", + e, c, upsample[c], half_width[c], tot_pre, tot, fabs(tot-tot_pre)/fabs(tot_pre)); + + gkyl_array_release(f); + } + gkyl_array_release(f_pre); + } +} + +void +test_zero_shift_is_identity(void) +{ + // Check that a zero shift leaves the ghost plane alone, for any upsampling. + struct ts_ctx tctx = { .offset = 0.0, .shear = 0.0 }; + double dx = (ts_upper[0]-ts_lower[0])/ts_cells[0]; + + for (int e=0; e<2; e++) { + enum gkyl_edge_loc edge = e == 0? GKYL_LOWER_EDGE : GKYL_UPPER_EDGE; + struct ts_setup s; + ts_setup_init(&s, edge); + + struct gkyl_array *f_pre = ts_donor_new(&s); + + int upsample[] = {1, 2, 4}; + for (int c=0; c<3; c++) { + int half_width = upsample[c] > 1? 4 : 0; + double cutoff = upsample[c] > 1? 2.0*dx/upsample[c] : 0.0; + struct gkyl_array *f = ts_run(&s, edge, upsample[c], half_width, cutoff, NULL, &tctx); + + // The donor plane is the ghost plane periodicity just filled, so the BC + // writing over it must reproduce what was already there. + double maxd = ts_max_diff(f_pre, f, &s.ghost_r, s.basis.num_basis); + TEST_CHECK( maxd < 1.0e-13 ); + TEST_MSG("edge %d, upsample %d: zero shift changed the plane by %.3e", e, upsample[c], maxd); + + gkyl_array_release(f); + } + gkyl_array_release(f_pre); + } +} + +void +test_zero_shear_is_cell_translation(void) +{ + // Check that a uniform shift is equivalent to a cell translation, for any upsampling. + int num_cells = 3; + double dy = (ts_upper[1]-ts_lower[1])/ts_cells[1]; + struct ts_ctx tctx = { .offset = num_cells*dy, .shear = 0.0 }; + + for (int e=0; e<2; e++) { + enum gkyl_edge_loc edge = e == 0? GKYL_LOWER_EDGE : GKYL_UPPER_EDGE; + struct ts_setup s; + ts_setup_init(&s, edge); + + struct gkyl_array *f_pre = ts_donor_new(&s); + struct gkyl_array *f = ts_run(&s, edge, 1, 0, 0.0, NULL, &tctx); + + double maxd = 0.0; + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &s.ghost_r); + while (gkyl_range_iter_next(&iter)) { + int idx_do[GKYL_MAX_DIM]; + gkyl_copy_int_arr(ts_ndim, iter.idx, idx_do); + int ny = ts_cells[1]; + idx_do[1] = ((iter.idx[1]-1-num_cells) % ny + ny) % ny + 1; + + const double *tar = gkyl_array_cfetch(f, gkyl_range_idx(&s.ghost_r, iter.idx)); + const double *don = gkyl_array_cfetch(f_pre, gkyl_range_idx(&s.ghost_r, idx_do)); + for (int k=0; k #include #include +#include #include #include #include static void -bc_twistshift_filter_enabled(struct gkyl_dg_lowpass_filter *filt_up, - struct gkyl_array *GKYL_RESTRICT finout, struct gkyl_array *GKYL_RESTRICT fbuff) +bc_twistshift_refine_enabled(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo) { - gkyl_dg_lowpass_filter_advance(filt_up, finout, fbuff); - gkyl_array_copy(finout, fbuff); + gkyl_dg_interpolate_advance(up->refine, fdo, up->ffine); } static void -bc_twistshift_filter_disabled(struct gkyl_dg_lowpass_filter *filt_up, - struct gkyl_array *GKYL_RESTRICT finout, struct gkyl_array *GKYL_RESTRICT fbuff) +bc_twistshift_refine_disabled(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo) { - // Do nothing. + gkyl_array_copy_range_to_range(up->ffine, fdo, &up->ghost_r, &up->coarse_ghost_r); } static void -bc_twistshift_refine_enabled(struct gkyl_dg_interpolate *refine, - struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) +bc_twistshift_coarsen_enabled(struct gkyl_bc_twistshift *up, struct gkyl_array *ftar) { - gkyl_dg_interpolate_advance(refine, fdo, ftar); + gkyl_dg_interpolate_advance(up->coarsen, up->ffine, ftar); } static void -bc_twistshift_refine_disabled(struct gkyl_dg_interpolate *refine, - struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) +bc_twistshift_coarsen_disabled(struct gkyl_bc_twistshift *up, struct gkyl_array *ftar) { - gkyl_array_copy(ftar, fdo); + gkyl_array_copy_range_to_range(ftar, up->ffine, &up->coarse_ghost_r, &up->ghost_r); } static void -bc_twistshift_coarsen_enabled(struct gkyl_dg_interpolate *coarsen, - struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) +bc_twistshift_advance_ts(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, + struct gkyl_array *ftar) { - gkyl_dg_interpolate_advance(coarsen, fdo, ftar); + gkyl_twistshift_dg_advance(up->ts, fdo, ftar); } static void -bc_twistshift_coarsen_disabled(struct gkyl_dg_interpolate *coarsen, - struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) +bc_twistshift_advance_ts_filtered(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, + struct gkyl_array *ftar) { - gkyl_array_copy(ftar, fdo); + up->refine_func(up, fdo); + gkyl_twistshift_dg_advance(up->ts, up->ffine, up->ffine); + gkyl_dg_lowpass_filter_advance(up->filter, up->ffine, up->filt_buff); + gkyl_array_copy_range(up->ffine, up->filt_buff, &up->ghost_r); + up->coarsen_func(up, ftar); +} + +static void +bc_twistshift_refine_shift(const struct gkyl_bc_twistshift_inp *inp, + const struct gkyl_rect_grid *ts_grid, const struct gkyl_range *shear_r_fine, + struct gkyl_array *shift_dg_fine) +{ + // Refine the DG shift to match the supersampled shear grid. + int shear_dir = inp->shear_dir; + int shift_poly_order = inp->shift_poly_order? inp->shift_poly_order : inp->basis->poly_order; + + struct gkyl_basis shift_b; + gkyl_cart_modal_serendip(&shift_b, 1, shift_poly_order); + assert(inp->shift_dg->ncomp == shift_b.num_basis); + assert(shift_dg_fine->ncomp == shift_b.num_basis); + assert(shift_dg_fine->size == shear_r_fine->volume); + + struct gkyl_rect_grid grid_do, grid_tar; + gkyl_rect_grid_init(&grid_do, 1, &inp->grid->lower[shear_dir], + &inp->grid->upper[shear_dir], &inp->grid->cells[shear_dir]); + gkyl_rect_grid_init(&grid_tar, 1, &ts_grid->lower[shear_dir], + &ts_grid->upper[shear_dir], &ts_grid->cells[shear_dir]); + + struct gkyl_range shear_r_do; + gkyl_range_init(&shear_r_do, 1, (int[]) {inp->bcdir_ext_update_r->lower[shear_dir]}, + (int[]) {inp->bcdir_ext_update_r->upper[shear_dir]}); + assert(inp->shift_dg->size == shear_r_do.volume); + + struct gkyl_dg_interpolate *interp = gkyl_dg_interpolate_new(1, &shift_b, + &grid_do, &grid_tar, &shear_r_do, shear_r_fine, (int[]) {0}, false); + gkyl_dg_interpolate_advance(interp, inp->shift_dg, shift_dg_fine); + gkyl_dg_interpolate_release(interp); } struct gkyl_bc_twistshift* @@ -59,34 +91,105 @@ gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp) up->filter_cutoff_wavelength = inp->filter_cutoff_wavelength; up->upsample_factor = inp->upsample_factor > 1 ? inp->upsample_factor : 1; - // Ensure that we use filter if we upsample. - if (up->upsample_factor > 1) { - assert(up->filter_half_width > 1); - assert(up->filter_cutoff_wavelength > 0.0); + up->filter = NULL; + up->filt_buff = NULL; + up->ffine = NULL; + up->shift_dg_fine = NULL; + up->refine = NULL; + up->coarsen = NULL; + up->refine_func = bc_twistshift_refine_disabled; + up->coarsen_func = bc_twistshift_coarsen_disabled; + + // A half-width of 1 is the identity kernel. + assert(up->filter_half_width != 1); + // Supersampling is only useful if there is filtering. + if (up->upsample_factor > 1) + assert(up->filter_half_width > 1 && up->filter_cutoff_wavelength > 0.0); + + if (up->filter_half_width == 0) { + // Plain twist-shift. + struct gkyl_twistshift_dg_inp tsinp = { + .bc_dir = inp->bc_dir, + .shift_dir = inp->shift_dir, + .shear_dir = inp->shear_dir, + .edge = inp->edge, + .cdim = inp->cdim, + .bcdir_ext_update_r = *inp->bcdir_ext_update_r, + .num_ghost = inp->num_ghost, + .basis = *inp->basis, + .grid = *inp->grid, + .shift_func = inp->shift_func, + .shift_func_ctx = inp->shift_func_ctx, + .shift_dg = inp->shift_dg, + .use_gpu = inp->use_gpu, + .shift_poly_order = inp->shift_poly_order, + }; + up->ts = gkyl_twistshift_dg_new(&tsinp); + up->advance_func = bc_twistshift_advance_ts; + return up; } - // Grid and update range for the twist-shift. + // Upsampling and filtering attributes. + assert(!inp->use_gpu); const int ndim = inp->bcdir_ext_update_r->ndim; + // Ghost plane this BC fills, on the field's own grid. + if (inp->edge == GKYL_LOWER_EDGE) + gkyl_range_shorten_from_above(&up->coarse_ghost_r, inp->bcdir_ext_update_r, + inp->bc_dir, inp->num_ghost[inp->bc_dir]); + else + gkyl_range_shorten_from_below(&up->coarse_ghost_r, inp->bcdir_ext_update_r, + inp->bc_dir, inp->num_ghost[inp->bc_dir]); + + // Grid supersampled along shear_dir. int fine_cells[GKYL_MAX_DIM]; for (int d=0; dgrid->cells[d]; fine_cells[inp->shear_dir] *= up->upsample_factor; gkyl_rect_grid_init(&up->ts_grid, ndim, inp->grid->lower, inp->grid->upper, fine_cells); - - struct gkyl_range fine_ext, fine_local; - gkyl_create_grid_ranges(&up->ts_grid, inp->num_ghost, &fine_ext, &fine_local); + // Range for supersampled cells. int flo[GKYL_MAX_DIM], fup[GKYL_MAX_DIM]; - for (int d=0; dcoarse_ghost_r.lower[d]; + fup[d] = up->coarse_ghost_r.upper[d]; + } + flo[inp->shear_dir] = (flo[inp->shear_dir]-1)*up->upsample_factor + 1; + fup[inp->shear_dir] = fup[inp->shear_dir]*up->upsample_factor; + gkyl_range_init(&up->ts_ext_r, ndim, flo, fup); + gkyl_sub_range_init(&up->ts_update_r, &up->ts_ext_r, flo, fup); + + up->ffine = gkyl_array_new(GKYL_DOUBLE, inp->basis->num_basis, up->ts_ext_r.volume); + up->filt_buff = gkyl_array_new(GKYL_DOUBLE, inp->basis->num_basis, up->ts_ext_r.volume); + + // Ghost plane on the supersampled grid. + if (inp->edge == GKYL_LOWER_EDGE) + gkyl_range_shorten_from_above(&up->ghost_r, &up->ts_update_r, inp->bc_dir, + inp->num_ghost[inp->bc_dir]); + else + gkyl_range_shorten_from_below(&up->ghost_r, &up->ts_update_r, inp->bc_dir, + inp->num_ghost[inp->bc_dir]); + + up->filter = gkyl_dg_lowpass_filter_new(inp->shear_dir, up->filter_half_width, + up->filter_cutoff_wavelength, inp->basis, &up->ts_grid, &up->ghost_r, inp->use_gpu); + + if (up->upsample_factor > 1) { + up->refine = gkyl_dg_interpolate_new(inp->cdim, inp->basis, inp->grid, &up->ts_grid, + &up->coarse_ghost_r, &up->ghost_r, inp->num_ghost, inp->use_gpu); + up->coarsen = gkyl_dg_interpolate_new(inp->cdim, inp->basis, &up->ts_grid, inp->grid, + &up->ghost_r, &up->coarse_ghost_r, inp->num_ghost, inp->use_gpu); + up->refine_func = bc_twistshift_refine_enabled; + up->coarsen_func = bc_twistshift_coarsen_enabled; } - flo[inp->bc_dir] = fine_ext.lower[inp->bc_dir]; - fup[inp->bc_dir] = fine_ext.upper[inp->bc_dir]; - gkyl_sub_range_init(&up->ts_update_r, &fine_ext, flo, fup); - up->ffine = gkyl_array_new(GKYL_DOUBLE, inp->basis->num_basis, fine_ext.volume); + // Need to upsample the DG shift too. + struct gkyl_array *shift_dg = inp->shift_dg; + if (inp->shift_dg && up->upsample_factor > 1) { + struct gkyl_range shear_r_fine; + gkyl_range_init(&shear_r_fine, 1, (int[]) {flo[inp->shear_dir]}, + (int[]) {fup[inp->shear_dir]}); + up->shift_dg_fine = gkyl_array_new(GKYL_DOUBLE, inp->shift_dg->ncomp, shear_r_fine.volume); + bc_twistshift_refine_shift(inp, &up->ts_grid, &shear_r_fine, up->shift_dg_fine); + shift_dg = up->shift_dg_fine; + } - // The pure twist-shift updater. struct gkyl_twistshift_dg_inp tsinp = { .bc_dir = inp->bc_dir, .shift_dir = inp->shift_dir, @@ -99,104 +202,36 @@ gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp) .grid = up->ts_grid, .shift_func = inp->shift_func, .shift_func_ctx = inp->shift_func_ctx, - .shift_dg = inp->shift_dg, + .shift_dg = shift_dg, .use_gpu = inp->use_gpu, .shift_poly_order = inp->shift_poly_order, }; up->ts = gkyl_twistshift_dg_new(&tsinp); - - // Ghost plane the twist-shift fills, on the ts grid. - if (inp->edge == GKYL_LOWER_EDGE) - gkyl_range_shorten_from_above(&up->ghost_r, &up->ts_update_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); - else - gkyl_range_shorten_from_below(&up->ghost_r, &up->ts_update_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); - - // Optional low-pass filter along shear_dir. - up->filter = NULL; - up->filt_buff = NULL; - up->filter_func = bc_twistshift_filter_disabled; - up->refine = NULL; - up->coarsen = NULL; - up->refine_func = bc_twistshift_refine_disabled; - up->coarsen_func = bc_twistshift_coarsen_disabled; - if (up->filter_half_width > 0) { - up->filt_buff = gkyl_array_new(GKYL_DOUBLE, inp->basis->num_basis, up->ffine->size); - up->filter = gkyl_dg_lowpass_filter_new(inp->shear_dir, up->filter_half_width, - up->filter_cutoff_wavelength, inp->basis, &up->ts_grid, &up->ghost_r, inp->use_gpu); - up->filter_func = bc_twistshift_filter_enabled; - // Optional refine/coarsen interpolators along shear_dir. - if (up->upsample_factor > 1) { - if (inp->edge == GKYL_LOWER_EDGE) - gkyl_range_shorten_from_above(&up->coarse_ghost_r, inp->bcdir_ext_update_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); - else - gkyl_range_shorten_from_below(&up->coarse_ghost_r, inp->bcdir_ext_update_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); - up->refine = gkyl_dg_interpolate_new(inp->cdim, inp->basis, inp->grid, &up->ts_grid, - &up->coarse_ghost_r, &up->ghost_r, inp->num_ghost, inp->use_gpu); - up->coarsen = gkyl_dg_interpolate_new(inp->cdim, inp->basis, &up->ts_grid, inp->grid, - &up->ghost_r, &up->coarse_ghost_r, inp->num_ghost, inp->use_gpu); - up->refine_func = bc_twistshift_refine_enabled; - up->coarsen_func = bc_twistshift_coarsen_enabled; - } - } + up->advance_func = bc_twistshift_advance_ts_filtered; return up; } -struct gkyl_bc_twistshift* -gkyl_bc_twistshift_new(int bc_dir, int shift_dir, int shear_dir, - enum gkyl_edge_loc edge, int cdim, const struct gkyl_range *bcdir_ext_update_r, const int *num_ghost, - const struct gkyl_basis *basis, const struct gkyl_rect_grid *grid, evalf_t shift_func, void *shift_func_ctx, - struct gkyl_array *shift_dg, int shift_poly_order, bool use_gpu) -{ - // Convenience wrapper around gkyl_bc_twistshift_inew, with the optional - // anti-aliasing (filter/upsampling) disabled. - struct gkyl_bc_twistshift_inp inp = { - .bc_dir = bc_dir, - .shift_dir = shift_dir, - .shear_dir = shear_dir, - .edge = edge, - .cdim = cdim, - .bcdir_ext_update_r = bcdir_ext_update_r, - .num_ghost = num_ghost, - .basis = basis, - .grid = grid, - .shift_func = shift_func, - .shift_func_ctx = shift_func_ctx, - .shift_dg = shift_dg, - .shift_poly_order = shift_poly_order, - .use_gpu = use_gpu, - .filter_half_width = 0, - .filter_cutoff_wavelength = 0.0, - .upsample_factor = 1, - }; - return gkyl_bc_twistshift_inew(&inp); -} - void gkyl_bc_twistshift_advance(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar) { - // Refine the data to the fine grid. - up->refine_func(up->refine, fdo, up->ffine); - // Apply twist-shift. - gkyl_twistshift_dg_advance(up->ts, up->ffine, up->ffine); - // Apply the low-pass filter. - up->filter_func(up->filter, up->ffine, up->filt_buff); - // Move back the data to the original grid. - up->coarsen_func(up->coarsen, up->ffine, ftar); + up->advance_func(up, fdo, ftar); } void gkyl_bc_twistshift_release(struct gkyl_bc_twistshift *up) { gkyl_twistshift_dg_release(up->ts); - gkyl_array_release(up->ffine); - if (up->filter_half_width > 0) { - gkyl_array_release(up->filt_buff); + if (up->filter) { gkyl_dg_lowpass_filter_release(up->filter); - if (up->upsample_factor > 1) { - gkyl_dg_interpolate_release(up->refine); - gkyl_dg_interpolate_release(up->coarsen); - } + gkyl_array_release(up->ffine); + gkyl_array_release(up->filt_buff); + } + if (up->shift_dg_fine) + gkyl_array_release(up->shift_dg_fine); + if (up->refine) { + gkyl_dg_interpolate_release(up->refine); + gkyl_dg_interpolate_release(up->coarsen); } gkyl_free(up); } diff --git a/gyrokinetic/zero/gkyl_bc_twistshift.h b/gyrokinetic/zero/gkyl_bc_twistshift.h index 37a4533d82..c5ce924623 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift.h @@ -26,11 +26,7 @@ struct gkyl_bc_twistshift_inp { bool use_gpu; // Whether to apply the BC using the GPU. // Optional inputs: int shift_poly_order; // Basis order for the DG representation of the shift. - // Optional anti-aliasing: a low-pass filter along shear_dir (enabled when - // filter_half_width > 0), applied on a grid supersampled by upsample_factor - // along shear_dir (used only when the filter is on). Both act on the shifted - // ghost plane only. - int filter_half_width; // Filter stencil half-width M in cells (0 = no filter). + int filter_half_width; // Filter stencil half-width M in fine cells (0 = no filter). double filter_cutoff_wavelength; // Filter cutoff wavelength (physical units). int upsample_factor; // Supersampling factor along shear_dir (0/1 = none). }; @@ -46,32 +42,6 @@ struct gkyl_bc_twistshift_inp { */ struct gkyl_bc_twistshift* gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp); -/** - * Create a new updater to apply twist-shift BCs, passing each argument separately. - * This is a convenience wrapper around gkyl_bc_twistshift_inew with the optional - * anti-aliasing (filter/upsampling) disabled. - * - * @param bc_dir Direction in which to apply this BC. - * @param shift_dir Direction of the shift. - * @param shear_dir Direction in which the shift varies (shear). - * @param edge Edge of to apply this BC at (lower/upper). - * @param cdim Configuration space dimensions. - * @param bcdir_ext_update_r Local range where to apply BC, extended in bc_dir. - * @param num_ghost Number of ghost cells in each direction. - * @param basis Basis of the field shifted. - * @param grid Grid the field shifted is defined on. - * @param shift_func Function defining the shift. - * @param shift_func_ctx Context for shift_func. - * @param shift_dg Discretized shift. - * @param shift_poly_order Basis order for the DG representation of the shift (optional). - * @param use_gpu Whether to apply the BC using the GPU. - * @return New updater pointer. - */ -struct gkyl_bc_twistshift* gkyl_bc_twistshift_new(int bc_dir, int shift_dir, int shear_dir, - enum gkyl_edge_loc edge, int cdim, const struct gkyl_range *bcdir_ext_update_r, const int *num_ghost, - const struct gkyl_basis *basis, const struct gkyl_rect_grid *grid, evalf_t shift_func, void *shift_func_ctx, - struct gkyl_array *shift_dg, int shift_poly_order, bool use_gpu); - /** * Apply the twist-shift periodic BC. Expects periodicity along bc_dir to have * been applied to the donor field beforehand. Can be used in-place. diff --git a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h index e0b39318fc..6bcfd4f95a 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h @@ -15,21 +15,25 @@ struct gkyl_bc_twistshift { bool use_gpu; // Whether to apply the BC on the GPU. struct gkyl_twistshift_dg *ts; // Pure twist-shift updater. - - struct gkyl_dg_lowpass_filter *filter; // Optional post-shift filter along shear_dir. + + void (*advance_func)(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar); + int filter_half_width; // Filter stencil half-width M in cells (0 = no filter). double filter_cutoff_wavelength; // Filter cutoff wavelength. - struct gkyl_range ghost_r; // Ghost plane the twist-shift fills. - struct gkyl_array *filt_buff; // Buffer for the filter. - void (*filter_func)(struct gkyl_dg_lowpass_filter *filt_up, struct gkyl_array *GKYL_RESTRICT finout, struct gkyl_array *GKYL_RESTRICT fbuff); + int upsample_factor; // Supersampling factor along shear_dir. + struct gkyl_dg_lowpass_filter *filter; // Post-shift filter along shear_dir. struct gkyl_rect_grid ts_grid; // Grid refined along shear_dir. - struct gkyl_range ts_update_r; // Update range on the ts_grid. - int upsample_factor; // Supersampling factor along shear_dir. + struct gkyl_range ts_ext_r; // ffine and filt_buff range. + struct gkyl_range ts_update_r; // Update range on ts_grid. + struct gkyl_range ghost_r; // Ghost plane the twist-shift fills, on ts_grid. + struct gkyl_range coarse_ghost_r; // Same plane on the field's own grid. + struct gkyl_array *ffine; // Ghost plane on the refined grid. + struct gkyl_array *filt_buff; // Buffer for the filter (shaped like ffine). + struct gkyl_array *shift_dg_fine; // Input shift refined onto the fine shear grid. + struct gkyl_dg_interpolate *refine; // Coarse ghost plane -> fine ghost plane. struct gkyl_dg_interpolate *coarsen; // Fine ghost plane -> coarse ghost plane. - struct gkyl_array *ffine; // Field on the fine grid. - struct gkyl_range coarse_ghost_r; // Ghost plane on the coarse grid (donor and target). - void (*refine_func)(struct gkyl_dg_interpolate *refine, struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar); - void (*coarsen_func)(struct gkyl_dg_interpolate *coarsen, struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar); + void (*refine_func)(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo); + void (*coarsen_func)(struct gkyl_bc_twistshift *up, struct gkyl_array *ftar); }; diff --git a/gyrokinetic/zero/twistshift_dg.c b/gyrokinetic/zero/twistshift_dg.c index 5d618229a6..8942c4de87 100644 --- a/gyrokinetic/zero/twistshift_dg.c +++ b/gyrokinetic/zero/twistshift_dg.c @@ -1780,6 +1780,9 @@ gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp) gkyl_eval_on_nodes_release(evup); } else { + // The shift must be discretized on the shear cells this updater indexes. + assert(inp->shift_dg->size == up->shear_r.volume); + assert(inp->shift_dg->ncomp == up->shift_b.num_basis); up->shift_dg = gkyl_array_acquire(inp->shift_dg); } From 6d5c083710a6c54912cb07256aa954e8d73639d6 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Wed, 29 Jul 2026 17:15:37 -0400 Subject: [PATCH 11/30] the half width of the filter is now expressed in term of the original grid, not the supersampled one --- gyrokinetic/apps/gkyl_gyrokinetic.h | 2 +- gyrokinetic/zero/bc_twistshift.c | 11 +++++++---- gyrokinetic/zero/gkyl_bc_twistshift.h | 2 +- gyrokinetic/zero/gkyl_bc_twistshift_priv.h | 4 +++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/gyrokinetic/apps/gkyl_gyrokinetic.h b/gyrokinetic/apps/gkyl_gyrokinetic.h index cd23b0c6fe..780cba59c4 100644 --- a/gyrokinetic/apps/gkyl_gyrokinetic.h +++ b/gyrokinetic/apps/gkyl_gyrokinetic.h @@ -252,7 +252,7 @@ struct gkyl_gyrokinetic_geometry { // Twist-shift anti-aliasing filter. int ts_upsample_factor; // Supersampling factor along the shear direction. - int ts_filter_half_width; // Filter stencil half-width in fine cells (0 = off, must be > 1). + int ts_filter_half_width; // Filter stencil half-width in cells of the simulation grid (0 = off). double ts_filter_cutoff_wavelength; // Filter cutoff wavelength (use 2*dx to cut at the coarse Nyquist). struct gkyl_efit_inp efit_info; // Context with RZ data such as efit file for a tokamak or mirror. diff --git a/gyrokinetic/zero/bc_twistshift.c b/gyrokinetic/zero/bc_twistshift.c index 72302c5a1d..17d75f0e54 100644 --- a/gyrokinetic/zero/bc_twistshift.c +++ b/gyrokinetic/zero/bc_twistshift.c @@ -100,11 +100,14 @@ gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp) up->refine_func = bc_twistshift_refine_disabled; up->coarsen_func = bc_twistshift_coarsen_disabled; - // A half-width of 1 is the identity kernel. - assert(up->filter_half_width != 1); + // The half-width counts cells of the original grid. + up->half_width_fine = up->filter_half_width * up->upsample_factor; + + // A stencil one fine cell wide is the identity kernel. + assert(up->half_width_fine != 1); // Supersampling is only useful if there is filtering. if (up->upsample_factor > 1) - assert(up->filter_half_width > 1 && up->filter_cutoff_wavelength > 0.0); + assert(up->filter_half_width > 0 && up->filter_cutoff_wavelength > 0.0); if (up->filter_half_width == 0) { // Plain twist-shift. @@ -167,7 +170,7 @@ gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp) gkyl_range_shorten_from_below(&up->ghost_r, &up->ts_update_r, inp->bc_dir, inp->num_ghost[inp->bc_dir]); - up->filter = gkyl_dg_lowpass_filter_new(inp->shear_dir, up->filter_half_width, + up->filter = gkyl_dg_lowpass_filter_new(inp->shear_dir, up->half_width_fine, up->filter_cutoff_wavelength, inp->basis, &up->ts_grid, &up->ghost_r, inp->use_gpu); if (up->upsample_factor > 1) { diff --git a/gyrokinetic/zero/gkyl_bc_twistshift.h b/gyrokinetic/zero/gkyl_bc_twistshift.h index c5ce924623..ac5dfefe1d 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift.h @@ -26,7 +26,7 @@ struct gkyl_bc_twistshift_inp { bool use_gpu; // Whether to apply the BC using the GPU. // Optional inputs: int shift_poly_order; // Basis order for the DG representation of the shift. - int filter_half_width; // Filter stencil half-width M in fine cells (0 = no filter). + int filter_half_width; // Filter stencil half-width M in cells of grid (0 = no filter). double filter_cutoff_wavelength; // Filter cutoff wavelength (physical units). int upsample_factor; // Supersampling factor along shear_dir (0/1 = none). }; diff --git a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h index 6bcfd4f95a..3b39317cfc 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift_priv.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift_priv.h @@ -18,7 +18,9 @@ struct gkyl_bc_twistshift { void (*advance_func)(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar); - int filter_half_width; // Filter stencil half-width M in cells (0 = no filter). + int filter_half_width; // Filter stencil half-width M in cells of the field's + // own grid (0 = no filter). + int half_width_fine; // The same stencil measured in supersampled cells. double filter_cutoff_wavelength; // Filter cutoff wavelength. int upsample_factor; // Supersampling factor along shear_dir. From 25e2b4daf2fa6824a67acc5cfccd11f94487875c Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Thu, 30 Jul 2026 10:22:24 -0400 Subject: [PATCH 12/30] Fix the treatment of the reflexion at the domain boundary. The filter at the boundary is not cropped but act on a reflexion of the data and this includes a change of sign of odd basis element coefficient that was missing. --- core/unit/ctest_dg_lowpass_filter.c | 52 +++++++++++++++++++++++++ core/zero/dg_lowpass_filter.c | 16 +++++++- core/zero/gkyl_dg_lowpass_filter_priv.h | 10 +++-- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/core/unit/ctest_dg_lowpass_filter.c b/core/unit/ctest_dg_lowpass_filter.c index 479b38cb0d..00ddb4d438 100644 --- a/core/unit/ctest_dg_lowpass_filter.c +++ b/core/unit/ctest_dg_lowpass_filter.c @@ -301,7 +301,59 @@ test_3x(void) gkyl_array_release(fout); } +static void +test_1x_reflection(void) +{ + // Verify that the reflexion at the boundary is done correctly. + int poly_order = 1; + int cells[] = {64}; + double lower[] = {0.0}, upper[] = {1.0}; + int nghost[] = {1}; + int M = 8; + + struct gkyl_rect_grid grid; + gkyl_rect_grid_init(&grid, 1, lower, upper, cells); + struct gkyl_basis basis; + gkyl_cart_modal_serendip(&basis, 1, poly_order); + + struct gkyl_range local, local_ext; + gkyl_create_grid_ranges(&grid, nghost, &local_ext, &local); + + struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, M, + grid.dx[0]/0.25, &basis, &grid, &local, false); + + struct gkyl_array *fin = mkarr(basis.num_basis, local_ext.volume); + struct gkyl_array *fout = mkarr(basis.num_basis, local_ext.volume); + + gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, + poly_order+1, 1, eval_linear_1x, NULL); + gkyl_proj_on_basis_advance(proj, 0.0, &local, fin); + gkyl_proj_on_basis_release(proj); + + gkyl_dg_lowpass_filter_advance(lpf, fin, fout); + + // Basis evaluated at the two cell edges, to take the jump across a face. + double b_lo[8], b_up[8]; + basis.eval((double[]) {-1.0}, b_lo); + basis.eval((double[]) { 1.0}, b_up); + + for (int i=local.lower[0]; iweights = gkyl_malloc((2*half_width+1)*sizeof(double)); dg_lpf_calc_weights(half_width, fc, up->weights); + // Sign each coefficient picks up when its donor cell is reached by mirroring. + up->sign_plain = gkyl_malloc(up->num_basis*sizeof(double)); + up->sign_mirror = gkyl_malloc(up->num_basis*sizeof(double)); + for (int c=0; cnum_basis; c++) up->sign_plain[c] = 1.0; + basis->flip_odd_sign(dir, up->sign_plain, up->sign_mirror); + return up; } @@ -63,15 +69,19 @@ gkyl_dg_lowpass_filter_advance(gkyl_dg_lowpass_filter *up, // Loop over the donor cells contributing to this target cell. for (int k=-M; krange.lower[dir], - up->range.upper[dir]); + up->range.upper[dir], &mirrored); long linidx_do = gkyl_range_idx(&up->range, idx_do); const double *fdo_c = gkyl_array_cfetch(fdo, linidx_do); + // Handle reflection. + const double *sgn = mirrored? up->sign_mirror : up->sign_plain; + double w = up->weights[k+M]; for (int c=0; cweights); + gkyl_free(up->sign_plain); + gkyl_free(up->sign_mirror); gkyl_free(up); } diff --git a/core/zero/gkyl_dg_lowpass_filter_priv.h b/core/zero/gkyl_dg_lowpass_filter_priv.h index b0a949acd0..788d5d60a2 100644 --- a/core/zero/gkyl_dg_lowpass_filter_priv.h +++ b/core/zero/gkyl_dg_lowpass_filter_priv.h @@ -17,16 +17,20 @@ struct gkyl_dg_lowpass_filter { struct gkyl_rect_grid grid; // Grid the field is defined on. struct gkyl_range range; // Range to filter in. double *weights; // 2M+1 filter weights, normalized to sum to 1. + double *sign_mirror; // Per-coefficient sign for a mirrored donor. + double *sign_plain; // All ones, for a donor that was not mirrored. }; static inline int -dg_lpf_mirror_idx(int idx, int lo, int up) +dg_lpf_mirror_idx(int idx, int lo, int up, bool *mirrored) { // Reflect an out-of-range index back in about the outer cell faces. + int num_reflections = 0; while (idx < lo || idx > up) { - if (idx < lo) idx = 2*lo - 1 - idx; - if (idx > up) idx = 2*up + 1 - idx; + if (idx < lo) { idx = 2*lo - 1 - idx; num_reflections++; } + if (idx > up) { idx = 2*up + 1 - idx; num_reflections++; } } + *mirrored = num_reflections % 2 == 1; return idx; } From 7b5c06eb3245a7b376ea225db8bd39486ba43095 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Fri, 31 Jul 2026 09:09:21 -0400 Subject: [PATCH 13/30] @manauref, this change was required in order to make the conservation test in the ctest_bc_twistshift run. It seems that there was a possibility of an infinite while loop here but I am not 100% sure of what was happening. The original twistshift unit test is still passing. --- gyrokinetic/zero/twistshift_dg.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gyrokinetic/zero/twistshift_dg.c b/gyrokinetic/zero/twistshift_dg.c index 8942c4de87..0c1baa11ce 100644 --- a/gyrokinetic/zero/twistshift_dg.c +++ b/gyrokinetic/zero/twistshift_dg.c @@ -407,19 +407,19 @@ ts_donor_target_offset(struct gkyl_twistshift_dg *up, const double *xc_do, const // y-offset between the donor and the target cell (yDo-yTar), in the direction of the shift. // xc_do: cell center coordinates of donor cell. // xc_tar: cell center coordinates of target cell. - int shear_dir = up->shear_dir_in_ts_grid; int shift_dir = up->shift_dir_in_ts_grid; double x_eval = xc_do[up->shear_dir]; double shift; up->shift_func(0.0, (double[]){x_eval}, &shift, up->shift_func_ctx); - int shift_sign = ts_sign(shift); double shift_dir_L = up->ts_grid.upper[up->shift_dir] - up->ts_grid.lower[up->shift_dir]; - + // The idea here is that we keep shifting the donor cell center until it is in a // periodic copy of our domain which overlaps with the shifted target cell center. double xs_shifted_do = xc_do[shift_dir]; double xs_shifted_tar = xc_tar[shift_dir] - shift; + // Step toward the target. + int shift_sign = xs_shifted_tar < xs_shifted_do? 1 : -1; bool keep_shifting = true; while (keep_shifting) { double xs_shifted_dolo = xs_shifted_do - shift_dir_L/2.0; From 2f26d86747ad28686337414c5b120fa3d9dcf55b Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Fri, 31 Jul 2026 09:27:52 -0400 Subject: [PATCH 14/30] add GPU infra for dg_lowpass_filter --- core/unit/ctest_dg_lowpass_filter.c | 131 ++++++++++++++++-------- core/zero/dg_lowpass_filter.c | 27 ++++- core/zero/dg_lowpass_filter_cu.cu | 59 +++++++++++ core/zero/gkyl_dg_lowpass_filter_priv.h | 10 ++ 4 files changed, 184 insertions(+), 43 deletions(-) create mode 100644 core/zero/dg_lowpass_filter_cu.cu diff --git a/core/unit/ctest_dg_lowpass_filter.c b/core/unit/ctest_dg_lowpass_filter.c index 00ddb4d438..a736de5aa5 100644 --- a/core/unit/ctest_dg_lowpass_filter.c +++ b/core/unit/ctest_dg_lowpass_filter.c @@ -16,9 +16,10 @@ #include static struct gkyl_array* -mkarr(long nc, long size) +mkarr(bool on_gpu, long nc, long size) { - struct gkyl_array* a = gkyl_array_new(GKYL_DOUBLE, nc, size); + struct gkyl_array* a = on_gpu? gkyl_array_cu_dev_new(GKYL_DOUBLE, nc, size) + : gkyl_array_new(GKYL_DOUBLE, nc, size); return a; } @@ -78,7 +79,7 @@ void eval_mode_3x(double t, const double *xn, double *fout, void *ctx) } static void -test_1x(void) +test_1x(bool use_gpu) { int poly_order = 1; int cells[] = {32}; @@ -96,51 +97,57 @@ test_1x(void) gkyl_create_grid_ranges(&grid, nghost, &local_ext, &local); struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, M, - grid.dx[0]/fc, &basis, &grid, &local, false); + grid.dx[0]/fc, &basis, &grid, &local, use_gpu); - struct gkyl_array *fin = mkarr(basis.num_basis, local_ext.volume); - struct gkyl_array *fout = mkarr(basis.num_basis, local_ext.volume); + struct gkyl_array *fin = mkarr(use_gpu, basis.num_basis, local_ext.volume); + struct gkyl_array *fout = mkarr(use_gpu, basis.num_basis, local_ext.volume); + struct gkyl_array *fin_ho = use_gpu? mkarr(false, fin->ncomp, fin->size) : gkyl_array_acquire(fin); + struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); // a) A constant field is preserved exactly everywhere, including at // the boundaries where the stencil is reflected. gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, poly_order+1, 1, eval_const_1x, NULL); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin); + gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); gkyl_proj_on_basis_release(proj); + gkyl_array_copy(fin, fin_ho); gkyl_array_clear(fout, 7.0); gkyl_dg_lowpass_filter_advance(lpf, fin, fout); + gkyl_array_copy(fout_ho, fout); struct gkyl_range_iter iter; gkyl_range_iter_init(&iter, &local); while (gkyl_range_iter_next(&iter)) { long linidx = gkyl_range_idx(&local, iter.idx); - const double *in_c = gkyl_array_cfetch(fin, linidx); - const double *out_c = gkyl_array_cfetch(fout, linidx); + const double *in_c = gkyl_array_cfetch(fin_ho, linidx); + const double *out_c = gkyl_array_cfetch(fout_ho, linidx); for (int c=0; c local.upper[0]-M) continue; long linidx = gkyl_range_idx(&local, iter.idx); - const double *in_c = gkyl_array_cfetch(fin, linidx); - const double *out_c = gkyl_array_cfetch(fout, linidx); + const double *in_c = gkyl_array_cfetch(fin_ho, linidx); + const double *out_c = gkyl_array_cfetch(fout_ho, linidx); for (int c=0; c local.upper[0]-M) continue; long linidx = gkyl_range_idx(&local, iter.idx); - const double *in_c = gkyl_array_cfetch(fin, linidx); - const double *out_c = gkyl_array_cfetch(fout, linidx); + const double *in_c = gkyl_array_cfetch(fin_ho, linidx); + const double *out_c = gkyl_array_cfetch(fout_ho, linidx); for (int c=0; c local.upper[0]-M) continue; - const double *out_c = gkyl_array_cfetch(fout, gkyl_range_idx(&local, iter.idx)); + const double *out_c = gkyl_array_cfetch(fout_ho, gkyl_range_idx(&local, iter.idx)); for (int c=0; cncomp, fin->size) : gkyl_array_acquire(fin); + struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); // A ramp peaks at one boundary, a boundary-hugging gaussian at the other. evalf_t evals[] = {eval_linear_1x, eval_gauss_edge_1x, eval_gauss_1x}; @@ -217,22 +230,24 @@ test_1x_conservation(void) for (int q=0; q<3; q++) { struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, half_widths[q], - grid.dx[0]/0.3, &basis, &grid, &local, false); + grid.dx[0]/0.3, &basis, &grid, &local, use_gpu); gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, poly_order+1, 1, evals[q], NULL); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin); + gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); gkyl_proj_on_basis_release(proj); + gkyl_array_copy(fin, fin_ho); gkyl_dg_lowpass_filter_advance(lpf, fin, fout); + gkyl_array_copy(fout_ho, fout); double tot_in = 0.0, tot_out = 0.0; struct gkyl_range_iter iter; gkyl_range_iter_init(&iter, &local); while (gkyl_range_iter_next(&iter)) { long linidx = gkyl_range_idx(&local, iter.idx); - tot_in += ((const double *) gkyl_array_cfetch(fin, linidx))[0]; - tot_out += ((const double *) gkyl_array_cfetch(fout, linidx))[0]; + tot_in += ((const double *) gkyl_array_cfetch(fin_ho, linidx))[0]; + tot_out += ((const double *) gkyl_array_cfetch(fout_ho, linidx))[0]; } TEST_CHECK( fabs(tot_out-tot_in) < 1e-12*fabs(tot_in) ); TEST_MSG("case %d (M=%d): total in %.13e | out %.13e | rel change %.3e", @@ -243,10 +258,12 @@ test_1x_conservation(void) gkyl_array_release(fin); gkyl_array_release(fout); + gkyl_array_release(fin_ho); + gkyl_array_release(fout_ho); } static void -test_3x(void) +test_3x(bool use_gpu) { // Filter along x of a 3D field. For a separable field g(x)*h(y,z) the // p=1 tensor coefficients factor too, so filtering in x scales the @@ -268,18 +285,22 @@ test_3x(void) gkyl_create_grid_ranges(&grid, nghost, &local_ext, &local); struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, M, - grid.dx[0]/fc, &basis, &grid, &local, false); + grid.dx[0]/fc, &basis, &grid, &local, use_gpu); - struct gkyl_array *fin = mkarr(basis.num_basis, local_ext.volume); - struct gkyl_array *fout = mkarr(basis.num_basis, local_ext.volume); + struct gkyl_array *fin = mkarr(use_gpu, basis.num_basis, local_ext.volume); + struct gkyl_array *fout = mkarr(use_gpu, basis.num_basis, local_ext.volume); + struct gkyl_array *fin_ho = use_gpu? mkarr(false, fin->ncomp, fin->size) : gkyl_array_acquire(fin); + struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); struct mode_ctx mctx = { .mode_num = 16.0 }; // x-Nyquist mode for 32 cells. gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, poly_order+1, 1, eval_mode_3x, &mctx); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin); + gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); gkyl_proj_on_basis_release(proj); + gkyl_array_copy(fin, fin_ho); gkyl_dg_lowpass_filter_advance(lpf, fin, fout); + gkyl_array_copy(fout_ho, fout); double freq = mctx.mode_num/cells[0]; // Cycles/cell. double gain = filter_gain(M, fc, freq); @@ -290,8 +311,8 @@ test_3x(void) if (iter.idx[0] < local.lower[0]+M || iter.idx[0] > local.upper[0]-M) continue; long linidx = gkyl_range_idx(&local, iter.idx); - const double *in_c = gkyl_array_cfetch(fin, linidx); - const double *out_c = gkyl_array_cfetch(fout, linidx); + const double *in_c = gkyl_array_cfetch(fin_ho, linidx); + const double *out_c = gkyl_array_cfetch(fout_ho, linidx); for (int c=0; cncomp, fin->size) : gkyl_array_acquire(fin); + struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, poly_order+1, 1, eval_linear_1x, NULL); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin); + gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); gkyl_proj_on_basis_release(proj); + gkyl_array_copy(fin, fin_ho); gkyl_dg_lowpass_filter_advance(lpf, fin, fout); + gkyl_array_copy(fout_ho, fout); // Basis evaluated at the two cell edges, to take the jump across a face. double b_lo[8], b_up[8]; @@ -338,8 +365,8 @@ test_1x_reflection(void) basis.eval((double[]) { 1.0}, b_up); for (int i=local.lower[0]; iuse_gpu = use_gpu; - assert(!up->use_gpu); // GPU implementation pending. up->ndim = basis->ndim; up->dir = dir; up->half_width = half_width; @@ -37,6 +36,18 @@ gkyl_dg_lowpass_filter_new(int dir, int half_width, double cutoff_wavelength, for (int c=0; cnum_basis; c++) up->sign_plain[c] = 1.0; basis->flip_odd_sign(dir, up->sign_plain, up->sign_mirror); + up->weights_cu = up->sign_plain_cu = up->sign_mirror_cu = NULL; +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + up->weights_cu = gkyl_cu_malloc((2*half_width+1)*sizeof(double)); + up->sign_plain_cu = gkyl_cu_malloc(up->num_basis*sizeof(double)); + up->sign_mirror_cu = gkyl_cu_malloc(up->num_basis*sizeof(double)); + gkyl_cu_memcpy(up->weights_cu, up->weights, (2*half_width+1)*sizeof(double), GKYL_CU_MEMCPY_H2D); + gkyl_cu_memcpy(up->sign_plain_cu, up->sign_plain, up->num_basis*sizeof(double), GKYL_CU_MEMCPY_H2D); + gkyl_cu_memcpy(up->sign_mirror_cu, up->sign_mirror, up->num_basis*sizeof(double), GKYL_CU_MEMCPY_H2D); + } +#endif + return up; } @@ -49,6 +60,13 @@ gkyl_dg_lowpass_filter_advance(gkyl_dg_lowpass_filter *up, assert(fdo->size == ftar->size); assert(fdo->ncomp == up->num_basis); +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + gkyl_dg_lowpass_filter_advance_cu(up, fdo, ftar); + return; + } +#endif + int dir = up->dir; int M = up->half_width; int num_basis = up->num_basis; @@ -93,5 +111,12 @@ gkyl_dg_lowpass_filter_release(gkyl_dg_lowpass_filter *up) gkyl_free(up->weights); gkyl_free(up->sign_plain); gkyl_free(up->sign_mirror); +#ifdef GKYL_HAVE_CUDA + if (up->use_gpu) { + gkyl_cu_free(up->weights_cu); + gkyl_cu_free(up->sign_plain_cu); + gkyl_cu_free(up->sign_mirror_cu); + } +#endif gkyl_free(up); } diff --git a/core/zero/dg_lowpass_filter_cu.cu b/core/zero/dg_lowpass_filter_cu.cu new file mode 100644 index 0000000000..aca4093995 --- /dev/null +++ b/core/zero/dg_lowpass_filter_cu.cu @@ -0,0 +1,59 @@ +/* -*- c++ -*- */ + +extern "C" { +#include +#include +#include +#include +} + +__global__ static void +gkyl_dg_lowpass_filter_advance_cu_ker(int dir, int M, int num_basis, + const double *GKYL_RESTRICT weights, const double *GKYL_RESTRICT sign_plain, + const double *GKYL_RESTRICT sign_mirror, struct gkyl_range range, + const struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar) +{ + int idx_tar[GKYL_MAX_DIM]; + int idx_do[GKYL_MAX_DIM]; + + for (unsigned long tid = threadIdx.x + blockIdx.x*blockDim.x; + tid < range.volume; tid += blockDim.x*gridDim.x) { + gkyl_sub_range_inv_idx(&range, tid, idx_tar); + + long linidx_tar = gkyl_range_idx(&range, idx_tar); + double *ftar_c = (double *) gkyl_array_fetch(ftar, linidx_tar); + + for (int c=0; crange.nblocks, nthreads = up->range.nthreads; + + gkyl_dg_lowpass_filter_advance_cu_ker<<>> + (up->dir, up->half_width, up->num_basis, up->weights_cu, up->sign_plain_cu, + up->sign_mirror_cu, up->range, fdo->on_dev, ftar->on_dev); +} diff --git a/core/zero/gkyl_dg_lowpass_filter_priv.h b/core/zero/gkyl_dg_lowpass_filter_priv.h index 788d5d60a2..f05ea5d877 100644 --- a/core/zero/gkyl_dg_lowpass_filter_priv.h +++ b/core/zero/gkyl_dg_lowpass_filter_priv.h @@ -19,8 +19,18 @@ struct gkyl_dg_lowpass_filter { double *weights; // 2M+1 filter weights, normalized to sum to 1. double *sign_mirror; // Per-coefficient sign for a mirrored donor. double *sign_plain; // All ones, for a donor that was not mirrored. + double *weights_cu; // Device copy of weights. + double *sign_mirror_cu; // Device copy of sign_mirror. + double *sign_plain_cu; // Device copy of sign_plain. }; +#ifdef GKYL_HAVE_CUDA +// Declaration of cuda device function. +void gkyl_dg_lowpass_filter_advance_cu(gkyl_dg_lowpass_filter *up, + struct gkyl_array *GKYL_RESTRICT fdo, struct gkyl_array *GKYL_RESTRICT ftar); +#endif + +GKYL_CU_DH static inline int dg_lpf_mirror_idx(int idx, int lo, int up, bool *mirrored) { From 8a44ace1ba8b4d711eed4b143f2aa2b9f272bf52 Mon Sep 17 00:00:00 2001 From: Antoinehoff Date: Mon, 3 Aug 2026 16:14:42 -0400 Subject: [PATCH 15/30] we relax the error verification on the CBC test because it was not passing on stellar CPU. We just verify the absolute error if the numbers are too small. --- gyrokinetic/unit/ctest_twistshift_dg.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/gyrokinetic/unit/ctest_twistshift_dg.c b/gyrokinetic/unit/ctest_twistshift_dg.c index 0c0da6902b..90661a3855 100644 --- a/gyrokinetic/unit/ctest_twistshift_dg.c +++ b/gyrokinetic/unit/ctest_twistshift_dg.c @@ -21,6 +21,17 @@ #include #include +// Compare two DG coefficients with a mixed relative/absolute tolerance. The +// relative check (gkyl_compare) blows up for coefficients near zero, where +// last-bit rounding differences across compilers/architectures (e.g. FMA +// contraction under -ffast-math on arm64 vs x86-64) dominate. Accept the value +// if either the relative or the absolute difference is within tolerance. +static bool +ts_compare_coeff(double ref, double val) +{ + return gkyl_compare(ref, val, 1e-13) || fabs(ref-val) < 1e-14; +} + // Meta-data for IO struct test_bc_twistshift_output_meta { int poly_order; // polynomial order @@ -3651,7 +3662,7 @@ test_bc_twistshift_3x_cbc_wcells(const int *cells, enum gkyl_edge_loc edge, double *f_c = gkyl_array_fetch(distf_ho, linidx); int refidx = iter.idx[1]-1; for (int k=0; k Date: Mon, 3 Aug 2026 16:22:28 -0400 Subject: [PATCH 16/30] remove dirichlet p=2 GPU test since there is an assert --- gyrokinetic/unit/ctest_fem_parproj.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gyrokinetic/unit/ctest_fem_parproj.c b/gyrokinetic/unit/ctest_fem_parproj.c index 29e8804ea3..07079285bd 100644 --- a/gyrokinetic/unit/ctest_fem_parproj.c +++ b/gyrokinetic/unit/ctest_fem_parproj.c @@ -1979,13 +1979,13 @@ TEST_LIST = { { "test_1x_p1_bcdirichlet_dev", test_1x_p1_bcdirichlet_dev }, { "test_1x_p1_bcperiodic_dev", test_1x_p1_bcperiodic_dev }, { "test_1x_p2_bcnone_dev", test_1x_p2_bcnone_dev }, - { "test_1x_p2_bcdirichlet_dev", test_1x_p2_bcdirichlet_dev }, + // { "test_1x_p2_bcdirichlet_dev", test_1x_p2_bcdirichlet_dev }, { "test_1x_p2_bcperiodic_dev", test_1x_p2_bcperiodic_dev }, { "test_2x_p1_bcnone_dev", test_2x_p1_bcnone_dev }, { "test_2x_p1_bcdirichlet_dev", test_2x_p1_bcdirichlet_dev }, { "test_2x_p1_bcperiodic_dev", test_2x_p1_bcperiodic_dev }, { "test_2x_p2_bcnone_dev", test_2x_p2_bcnone_dev }, - { "test_2x_p2_bcdirichlet_dev", test_2x_p2_bcdirichlet_dev }, + // { "test_2x_p2_bcdirichlet_dev", test_2x_p2_bcdirichlet_dev }, { "test_2x_p2_bcperiodic_dev", test_2x_p2_bcperiodic_dev }, { "test_2x_p1_weighted_dev_dev", test_2x_p1_weighted_dev}, { "test_2x_p1_selfadjoint_dev", test_2x_p1_selfadjoint_dev}, @@ -1995,7 +1995,7 @@ TEST_LIST = { { "test_3x_p1_bcperiodic_dev", test_3x_p1_bcperiodic_dev }, { "test_3x_p1_bcdirichlet_bias_dev", test_3x_p1_bcdirichlet_bias_dev }, { "test_3x_p2_bcnone_dev", test_3x_p2_bcnone_dev }, - { "test_3x_p2_bcdirichlet_dev", test_3x_p2_bcdirichlet_dev }, + // { "test_3x_p2_bcdirichlet_dev", test_3x_p2_bcdirichlet_dev }, { "test_3x_p2_bcperiodic_dev", test_3x_p2_bcperiodic_dev }, #endif { NULL, NULL }, From efdbb5caf422b654290ec64c0bbc372d742a9573 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Mon, 3 Aug 2026 16:29:58 -0400 Subject: [PATCH 17/30] comment adjustments --- gyrokinetic/unit/ctest_twistshift_dg.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gyrokinetic/unit/ctest_twistshift_dg.c b/gyrokinetic/unit/ctest_twistshift_dg.c index 90661a3855..6c8b50e1d6 100644 --- a/gyrokinetic/unit/ctest_twistshift_dg.c +++ b/gyrokinetic/unit/ctest_twistshift_dg.c @@ -21,11 +21,8 @@ #include #include -// Compare two DG coefficients with a mixed relative/absolute tolerance. The -// relative check (gkyl_compare) blows up for coefficients near zero, where -// last-bit rounding differences across compilers/architectures (e.g. FMA -// contraction under -ffast-math on arm64 vs x86-64) dominate. Accept the value -// if either the relative or the absolute difference is within tolerance. +// Compare two DG coefficients with a mixed relative/absolute tolerance. +// Needed to pass the CBC 3x test on stellar nvcc installation. static bool ts_compare_coeff(double ref, double val) { From 05d92820eafe04e309a9754c421ae73996c52cee Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Mon, 3 Aug 2026 16:49:04 -0400 Subject: [PATCH 18/30] fix another gk reg test, this one was failing on stellar only for both CPU and GPU. We remove the allocation of Bmag since it is unused and fix the evaluation of bmag at the end of the gk tests. --- gyrokinetic/unit/ctest_dg_interpolate.c | 69 +++++-------------------- 1 file changed, 12 insertions(+), 57 deletions(-) diff --git a/gyrokinetic/unit/ctest_dg_interpolate.c b/gyrokinetic/unit/ctest_dg_interpolate.c index 62147b12ab..03dd8a703f 100644 --- a/gyrokinetic/unit/ctest_dg_interpolate.c +++ b/gyrokinetic/unit/ctest_dg_interpolate.c @@ -974,15 +974,6 @@ test_1x1v_gk(const int *cells, const int *cells_tar, int poly_order, bool use_gp struct gkyl_range local, local_ext; // local, local-ext phase-space ranges gkyl_create_grid_ranges(&grid, ghost, &local_ext, &local); - // Create bmag arrays. - struct gkyl_array *bmag = mkarr(use_gpu, confBasis.num_basis, confLocal_ext.volume); - struct gkyl_array *bmag_ho = use_gpu? mkarr(false, bmag->ncomp, bmag->size) - : gkyl_array_acquire(bmag); - gkyl_proj_on_basis *proj_bmag = gkyl_proj_on_basis_new(&confGrid, &confBasis, - poly_order+1, 1, eval_bfield_1x, &proj_ctx); - gkyl_proj_on_basis_advance(proj_bmag, 0.0, &confLocal, bmag_ho); - gkyl_array_copy(bmag, bmag_ho); - // Create distribution function arrays. struct gkyl_array *distf = mkarr(use_gpu, basis.num_basis, local_ext.volume); struct gkyl_array *distf_ho = use_gpu? mkarr(false, distf->ncomp, distf->size) @@ -1136,11 +1127,8 @@ test_1x1v_gk(const int *cells, const int *cells_tar, int poly_order, bool use_gp gkyl_array_release(moms); gkyl_velocity_map_release(gvm); gkyl_gk_geometry_release(gk_geom); - gkyl_array_release(bmag); gkyl_array_release(distf); - gkyl_array_release(bmag_ho); gkyl_array_release(distf_ho); - gkyl_proj_on_basis_release(proj_bmag); gkyl_proj_on_basis_release(proj_distf); } @@ -1167,10 +1155,11 @@ void eval_distf_1x2v_gk(double t, const double *xn, double* restrict fout, void double vtsq = temp/mass; - double bmag[1] = {-1.0}; - eval_bfield_1x(t, xn, bmag, ctx); + double bfield[3] = {0.0}; + eval_bfield_1x(t, xn, bfield, ctx); + double bmag = sqrt(bfield[0]*bfield[0]+bfield[1]*bfield[1]+bfield[2]*bfield[2]); - fout[0] = (den/pow(2.0*M_PI*vtsq,vdim/2.0)) * exp(-(pow(vpar-upar,2)+2.0*mu*bmag[0]/mass)/(2.0*vtsq)); + fout[0] = (den/pow(2.0*M_PI*vtsq,vdim/2.0)) * exp(-(pow(vpar-upar,2)+2.0*mu*bmag/mass)/(2.0*vtsq)); } void @@ -1247,15 +1236,6 @@ test_1x2v_gk(const int *cells, const int *cells_tar, int poly_order, bool use_gp struct gkyl_range local, local_ext; // local, local-ext phase-space ranges gkyl_create_grid_ranges(&grid, ghost, &local_ext, &local); - // Create bmag arrays. - struct gkyl_array *bmag = mkarr(use_gpu, confBasis.num_basis, confLocal_ext.volume); - struct gkyl_array *bmag_ho = use_gpu? mkarr(false, bmag->ncomp, bmag->size) - : gkyl_array_acquire(bmag); - gkyl_proj_on_basis *proj_bmag = gkyl_proj_on_basis_new(&confGrid, &confBasis, - poly_order+1, 1, eval_bfield_1x, &proj_ctx); - gkyl_proj_on_basis_advance(proj_bmag, 0.0, &confLocal, bmag_ho); - gkyl_array_copy(bmag, bmag_ho); - // Create distribution function arrays. struct gkyl_array *distf = mkarr(use_gpu, basis.num_basis, local_ext.volume); struct gkyl_array *distf_ho = use_gpu? mkarr(false, distf->ncomp, distf->size) @@ -1406,11 +1386,8 @@ test_1x2v_gk(const int *cells, const int *cells_tar, int poly_order, bool use_gp gkyl_array_release(moms); gkyl_velocity_map_release(gvm); gkyl_gk_geometry_release(gk_geom); - gkyl_array_release(bmag); gkyl_array_release(distf); - gkyl_array_release(bmag_ho); gkyl_array_release(distf_ho); - gkyl_proj_on_basis_release(proj_bmag); gkyl_proj_on_basis_release(proj_distf); } @@ -1437,10 +1414,11 @@ void eval_distf_2x2v_gk(double t, const double *xn, double* restrict fout, void double vtsq = temp/mass; - double bmag[1] = {-1.0}; - eval_bfield_2x(t, xn, bmag, ctx); + double bfield[3] = {0.0}; + eval_bfield_2x(t, xn, bfield, ctx); + double bmag = sqrt(bfield[0]*bfield[0]+bfield[1]*bfield[1]+bfield[2]*bfield[2]); - fout[0] = (den/pow(2.0*M_PI*vtsq,vdim/2.0)) * exp(-(pow(vpar-upar,2)+2.0*mu*bmag[0]/mass)/(2.0*vtsq)); + fout[0] = (den/pow(2.0*M_PI*vtsq,vdim/2.0)) * exp(-(pow(vpar-upar,2)+2.0*mu*bmag/mass)/(2.0*vtsq)); } void @@ -1526,15 +1504,6 @@ test_2x2v_gk(const int *cells, const int *cells_tar, int poly_order, bool use_gp struct gkyl_range local, local_ext; // local, local-ext phase-space ranges gkyl_create_grid_ranges(&grid, ghost, &local_ext, &local); - // Create bmag arrays. - struct gkyl_array *bmag = mkarr(use_gpu, confBasis.num_basis, confLocal_ext.volume); - struct gkyl_array *bmag_ho = use_gpu? mkarr(false, bmag->ncomp, bmag->size) - : gkyl_array_acquire(bmag); - gkyl_proj_on_basis *proj_bmag = gkyl_proj_on_basis_new(&confGrid, &confBasis, - poly_order+1, 1, eval_bfield_2x, &proj_ctx); - gkyl_proj_on_basis_advance(proj_bmag, 0.0, &confLocal, bmag_ho); - gkyl_array_copy(bmag, bmag_ho); - // Create distribution function arrays. struct gkyl_array *distf = mkarr(use_gpu, basis.num_basis, local_ext.volume); struct gkyl_array *distf_ho = use_gpu? mkarr(false, distf->ncomp, distf->size) @@ -1685,11 +1654,8 @@ test_2x2v_gk(const int *cells, const int *cells_tar, int poly_order, bool use_gp gkyl_array_release(moms); gkyl_velocity_map_release(gvm); gkyl_gk_geometry_release(gk_geom); - gkyl_array_release(bmag); gkyl_array_release(distf); - gkyl_array_release(bmag_ho); gkyl_array_release(distf_ho); - gkyl_proj_on_basis_release(proj_bmag); gkyl_proj_on_basis_release(proj_distf); } @@ -1716,10 +1682,11 @@ void eval_distf_3x2v_gk(double t, const double *xn, double* restrict fout, void double vtsq = temp/mass; - double bmag[1] = {-1.0}; - eval_bfield_3x(t, xn, bmag, ctx); + double bfield[3] = {0.0}; + eval_bfield_3x(t, xn, bfield, ctx); + double bmag = sqrt(bfield[0]*bfield[0]+bfield[1]*bfield[1]+bfield[2]*bfield[2]); - fout[0] = (den/pow(2.0*M_PI*vtsq,vdim/2.0)) * exp(-(pow(vpar-upar,2)+2.0*mu*bmag[0]/mass)/(2.0*vtsq)); + fout[0] = (den/pow(2.0*M_PI*vtsq,vdim/2.0)) * exp(-(pow(vpar-upar,2)+2.0*mu*bmag/mass)/(2.0*vtsq)); } void @@ -1819,15 +1786,6 @@ test_3x2v_gk(const int *cells, const int *cells_tar, int poly_order, bool use_gp struct gkyl_range local, local_ext; // local, local-ext phase-space ranges gkyl_create_grid_ranges(&grid, ghost, &local_ext, &local); - // Create bmag arrays. - struct gkyl_array *bmag = mkarr(use_gpu, confBasis.num_basis, confLocal_ext.volume); - struct gkyl_array *bmag_ho = use_gpu? mkarr(false, bmag->ncomp, bmag->size) - : gkyl_array_acquire(bmag); - gkyl_proj_on_basis *proj_bmag = gkyl_proj_on_basis_new(&confGrid, &confBasis, - poly_order+1, 1, eval_bfield_3x, &proj_ctx); - gkyl_proj_on_basis_advance(proj_bmag, 0.0, &confLocal, bmag_ho); - gkyl_array_copy(bmag, bmag_ho); - // Create distribution function arrays. struct gkyl_array *distf = mkarr(use_gpu, basis.num_basis, local_ext.volume); struct gkyl_array *distf_ho = use_gpu? mkarr(false, distf->ncomp, distf->size) @@ -1978,11 +1936,8 @@ test_3x2v_gk(const int *cells, const int *cells_tar, int poly_order, bool use_gp gkyl_array_release(moms); gkyl_velocity_map_release(gvm); gkyl_gk_geometry_release(gk_geom); - gkyl_array_release(bmag); gkyl_array_release(distf); - gkyl_array_release(bmag_ho); gkyl_array_release(distf_ho); - gkyl_proj_on_basis_release(proj_bmag); gkyl_proj_on_basis_release(proj_distf); } From 3623974a4286a129871d496f4cea4ef7b044b741 Mon Sep 17 00:00:00 2001 From: Antoinehoff Date: Tue, 4 Aug 2026 11:29:35 -0700 Subject: [PATCH 19/30] remove the assert for using GPU with filter --- gyrokinetic/zero/bc_twistshift.c | 1 - 1 file changed, 1 deletion(-) diff --git a/gyrokinetic/zero/bc_twistshift.c b/gyrokinetic/zero/bc_twistshift.c index 17d75f0e54..0e9fbbb5c4 100644 --- a/gyrokinetic/zero/bc_twistshift.c +++ b/gyrokinetic/zero/bc_twistshift.c @@ -133,7 +133,6 @@ gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp) } // Upsampling and filtering attributes. - assert(!inp->use_gpu); const int ndim = inp->bcdir_ext_update_r->ndim; // Ghost plane this BC fills, on the field's own grid. if (inp->edge == GKYL_LOWER_EDGE) From 7cd5e58e6f243ed9020bf04d11d0138d4dc664a4 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Tue, 4 Aug 2026 15:07:53 -0400 Subject: [PATCH 20/30] pute the filtered arrays and buffer on GPU possibly --- gyrokinetic/zero/bc_twistshift.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gyrokinetic/zero/bc_twistshift.c b/gyrokinetic/zero/bc_twistshift.c index 0e9fbbb5c4..40c475d8fc 100644 --- a/gyrokinetic/zero/bc_twistshift.c +++ b/gyrokinetic/zero/bc_twistshift.c @@ -7,6 +7,14 @@ #include +// allocate array (filled with zeros) +static inline struct gkyl_array* +mkarr(bool use_gpu, long nc, long size) +{ + return use_gpu? gkyl_array_cu_dev_new(GKYL_DOUBLE, nc, size) + : gkyl_array_new(GKYL_DOUBLE, nc, size); +} + static void bc_twistshift_refine_enabled(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo) { @@ -158,8 +166,8 @@ gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp) gkyl_range_init(&up->ts_ext_r, ndim, flo, fup); gkyl_sub_range_init(&up->ts_update_r, &up->ts_ext_r, flo, fup); - up->ffine = gkyl_array_new(GKYL_DOUBLE, inp->basis->num_basis, up->ts_ext_r.volume); - up->filt_buff = gkyl_array_new(GKYL_DOUBLE, inp->basis->num_basis, up->ts_ext_r.volume); + up->ffine = mkarr(inp->use_gpu, inp->basis->num_basis, up->ts_ext_r.volume); + up->filt_buff = mkarr(inp->use_gpu, inp->basis->num_basis, up->ts_ext_r.volume); // Ghost plane on the supersampled grid. if (inp->edge == GKYL_LOWER_EDGE) From d7c44c424fba074a733ff4093ad6ced98631278a Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Tue, 4 Aug 2026 16:18:31 -0400 Subject: [PATCH 21/30] forgot to pass the filter parameters for IWL case --- gyrokinetic/apps/gk_field_2x3x.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gyrokinetic/apps/gk_field_2x3x.c b/gyrokinetic/apps/gk_field_2x3x.c index 4a0cbec236..44433d0118 100644 --- a/gyrokinetic/apps/gk_field_2x3x.c +++ b/gyrokinetic/apps/gk_field_2x3x.c @@ -370,6 +370,9 @@ gk_field_2x3x_add_IWL_updaters(struct gkyl_gyrokinetic_app *app, struct gk_field .basis = &app->basis, .grid = &app->grid, .use_gpu = app->use_gpu, + .upsample_factor = app->ts_upsample_factor, + .filter_half_width = app->ts_filter_half_width, + .filter_cutoff_wavelength = app->ts_filter_cutoff_wavelength, }; if (app->gk_geom->geometry_id == GKYL_GEOMETRY_TOKAMAK) T_LU_lo.shift_dg = app->delta_ts_x_lo; From e4b17f872f79a3ac8aaf65f6728d6e69f2ce1859 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Thu, 6 Aug 2026 09:45:44 -0400 Subject: [PATCH 22/30] forgot to pass the filter parameters in IWL for species :') --- gyrokinetic/apps/gk_species.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gyrokinetic/apps/gk_species.c b/gyrokinetic/apps/gk_species.c index c65507e738..0f2a090b04 100644 --- a/gyrokinetic/apps/gk_species.c +++ b/gyrokinetic/apps/gk_species.c @@ -1023,6 +1023,9 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app .basis = &gks->basis, .grid = &gks->grid, .use_gpu = app->use_gpu, + .upsample_factor = app->ts_upsample_factor, + .filter_half_width = app->ts_filter_half_width, + .filter_cutoff_wavelength = app->ts_filter_cutoff_wavelength, }; if (app->gk_geom->geometry_id == GKYL_GEOMETRY_TOKAMAK) tsinp_lo.shift_dg = app->delta_ts_x_lo; @@ -1043,6 +1046,9 @@ gk_species_init_dynamic(struct gkyl_gk *gk_app_inp, struct gkyl_gyrokinetic_app .basis = &gks->basis, .grid = &gks->grid, .use_gpu = app->use_gpu, + .upsample_factor = app->ts_upsample_factor, + .filter_half_width = app->ts_filter_half_width, + .filter_cutoff_wavelength = app->ts_filter_cutoff_wavelength, }; if (app->gk_geom->geometry_id == GKYL_GEOMETRY_TOKAMAK) tsinp_up.shift_dg = app->delta_ts_x_up; From 1405ac722e58bbe599a5b0d9aef2919154b9486a Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Thu, 6 Aug 2026 11:08:48 -0400 Subject: [PATCH 23/30] Add the possibility to truncate the stencil instead of reflect it --- core/unit/ctest_dg_lowpass_filter.c | 65 +++++++++++++++++++++++++ core/zero/dg_lowpass_filter.c | 26 ++++++++-- core/zero/dg_lowpass_filter_cu.cu | 28 +++++++++-- core/zero/gkyl_dg_lowpass_filter.h | 2 +- core/zero/gkyl_dg_lowpass_filter_priv.h | 2 + 5 files changed, 114 insertions(+), 9 deletions(-) diff --git a/core/unit/ctest_dg_lowpass_filter.c b/core/unit/ctest_dg_lowpass_filter.c index a736de5aa5..cf3535a8f8 100644 --- a/core/unit/ctest_dg_lowpass_filter.c +++ b/core/unit/ctest_dg_lowpass_filter.c @@ -381,13 +381,76 @@ test_1x_reflection(bool use_gpu) gkyl_array_release(fout_ho); } +static void +test_1x_interior(bool use_gpu) +{ + // Over a range whose edges are interior (not the domain boundary), the + // stencil is truncated and renormalized rather than reflected. Check that a + // constant is preserved exactly, i.e. the renormalization keeps the zero mode + // even where donors are dropped at the non-physical faces. + int poly_order = 1; + int cells[] = {64}; + double lower[] = {0.0}, upper[] = {1.0}; + int nghost[] = {1}; + int M = 8; + + struct gkyl_rect_grid grid; + gkyl_rect_grid_init(&grid, 1, lower, upper, cells); + struct gkyl_basis basis; + gkyl_cart_modal_serendip(&basis, 1, poly_order); + + struct gkyl_range local, local_ext; + gkyl_create_grid_ranges(&grid, nghost, &local_ext, &local); + + // Sub-range with both edges interior (away from the domain boundary). + struct gkyl_range sub; + gkyl_sub_range_init(&sub, &local, (int[]) {17}, (int[]) {48}); + + struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, M, + grid.dx[0]/0.25, &basis, &grid, &sub, use_gpu); + + struct gkyl_array *fin = mkarr(use_gpu, basis.num_basis, local_ext.volume); + struct gkyl_array *fout = mkarr(use_gpu, basis.num_basis, local_ext.volume); + struct gkyl_array *fin_ho = use_gpu? mkarr(false, fin->ncomp, fin->size) : gkyl_array_acquire(fin); + struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); + + gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, + poly_order+1, 1, eval_const_1x, NULL); + gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); + gkyl_proj_on_basis_release(proj); + gkyl_array_copy(fin, fin_ho); + + gkyl_dg_lowpass_filter_advance(lpf, fin, fout); + gkyl_array_copy(fout_ho, fout); + + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, &sub); + while (gkyl_range_iter_next(&iter)) { + long linidx = gkyl_range_idx(&sub, iter.idx); + const double *in_c = gkyl_array_cfetch(fin_ho, linidx); + const double *out_c = gkyl_array_cfetch(fout_ho, linidx); + for (int c=0; cgrid = *grid; up->range = *range; + // Reflect the stencil only at faces that coincide with the domain boundary. + up->reflect_lo = range->lower[dir] == 1; + up->reflect_up = range->upper[dir] == grid->cells[dir]; + // Perform some basic checks: assert(grid->ndim == range->ndim); assert(0 <= dir && dir < grid->ndim); @@ -86,10 +90,21 @@ gkyl_dg_lowpass_filter_advance(gkyl_dg_lowpass_filter *up, ftar_c[c] = 0.0; // Loop over the donor cells contributing to this target cell. + double wsum = 0.0; for (int k=-M; krange.lower[dir], - up->range.upper[dir], &mirrored); + bool mirrored = false; + int idx_k = iter.idx[dir]+k; + if (idx_k < up->range.lower[dir]) { + if (!up->reflect_lo) continue; // Interior edge: drop donor, renormalize. + idx_do[dir] = dg_lpf_mirror_idx(idx_k, up->range.lower[dir], up->range.upper[dir], &mirrored); + } + else if (idx_k > up->range.upper[dir]) { + if (!up->reflect_up) continue; // Interior edge: drop donor, renormalize. + idx_do[dir] = dg_lpf_mirror_idx(idx_k, up->range.lower[dir], up->range.upper[dir], &mirrored); + } + else { + idx_do[dir] = idx_k; + } long linidx_do = gkyl_range_idx(&up->range, idx_do); const double *fdo_c = gkyl_array_cfetch(fdo, linidx_do); @@ -98,9 +113,14 @@ gkyl_dg_lowpass_filter_advance(gkyl_dg_lowpass_filter *up, const double *sgn = mirrored? up->sign_mirror : up->sign_plain; double w = up->weights[k+M]; + wsum += w; for (int c=0; c range.upper[dir]) { + if (!reflect_up) continue; // Interior edge: drop donor, renormalize. + idx_do[dir] = dg_lpf_mirror_idx(idx_k, range.lower[dir], range.upper[dir], &mirrored); + } + else { + idx_do[dir] = idx_k; + } long linidx_do = gkyl_range_idx(&range, idx_do); const double *fdo_c = (const double *) gkyl_array_cfetch(fdo, linidx_do); @@ -41,9 +53,14 @@ gkyl_dg_lowpass_filter_advance_cu_ker(int dir, int M, int num_basis, const double *sgn = mirrored? sign_mirror : sign_plain; double w = weights[k+M]; + wsum += w; for (int c=0; crange.nblocks, nthreads = up->range.nthreads; gkyl_dg_lowpass_filter_advance_cu_ker<<>> - (up->dir, up->half_width, up->num_basis, up->weights_cu, up->sign_plain_cu, - up->sign_mirror_cu, up->range, fdo->on_dev, ftar->on_dev); + (up->dir, up->half_width, up->num_basis, up->reflect_lo, up->reflect_up, + up->weights_cu, up->sign_plain_cu, up->sign_mirror_cu, up->range, + fdo->on_dev, ftar->on_dev); } diff --git a/core/zero/gkyl_dg_lowpass_filter.h b/core/zero/gkyl_dg_lowpass_filter.h index 43562b5d79..8620d14d1b 100644 --- a/core/zero/gkyl_dg_lowpass_filter.h +++ b/core/zero/gkyl_dg_lowpass_filter.h @@ -18,7 +18,7 @@ typedef struct gkyl_dg_lowpass_filter gkyl_dg_lowpass_filter; * cutoff f_c = dx/cutoff_wavelength must satisfy 0 < f_c <= 0.5. * @param basis DG basis of the filtered field. * @param grid Grid the filtered field is defined on. - * @param range Range to filter in. The stencil is reflected at its boundaries. + * @param range Range to filter in. The stencil is reflected or truncated at the edges. * @param use_gpu bool to determine if on GPU. * @return New filter updater. */ diff --git a/core/zero/gkyl_dg_lowpass_filter_priv.h b/core/zero/gkyl_dg_lowpass_filter_priv.h index f05ea5d877..a2b9052f2a 100644 --- a/core/zero/gkyl_dg_lowpass_filter_priv.h +++ b/core/zero/gkyl_dg_lowpass_filter_priv.h @@ -16,6 +16,8 @@ struct gkyl_dg_lowpass_filter { int num_basis; // Number of DG coefficients per cell. struct gkyl_rect_grid grid; // Grid the field is defined on. struct gkyl_range range; // Range to filter in. + bool reflect_lo; // Whether to reflect the stencil at the lower edge. + bool reflect_up; // Whether to reflect the stencil at the upper edge. double *weights; // 2M+1 filter weights, normalized to sum to 1. double *sign_mirror; // Per-coefficient sign for a mirrored donor. double *sign_plain; // All ones, for a donor that was not mirrored. From d6e0b28fffa4d869a4e25be6169fd30e02ec51f9 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Tue, 25 Aug 2026 15:30:40 +0200 Subject: [PATCH 24/30] reorganize the lowpass filter test. I realize that the properties sum weight=1 conserves the 0 mode but this is not equivalent to conserving the density number if we are not considering a periodic field. In IWL sim, x is not periodic, so this filter breaks particle conservation :( I need to see how to fix this. --- core/unit/ctest_dg_lowpass_filter.c | 583 +++++++++++----------------- 1 file changed, 226 insertions(+), 357 deletions(-) diff --git a/core/unit/ctest_dg_lowpass_filter.c b/core/unit/ctest_dg_lowpass_filter.c index cf3535a8f8..dc91b3738b 100644 --- a/core/unit/ctest_dg_lowpass_filter.c +++ b/core/unit/ctest_dg_lowpass_filter.c @@ -1,7 +1,4 @@ -// Test the dg_lowpass_filter updater: a low-pass Blackman-windowed sinc FIR -// filter applied along one direction of a DG field, meant e.g. to de-alias -// the twist-shift BC by removing content beyond the coarse-grid resolution -// before restriction. +// Test the dg_lowpass_filter updater. #include #include @@ -15,6 +12,9 @@ #include +#define FILTER_DIR 0 // Direction filtered in every test below. +#define FOUT_FILL 7.0 // Value fout is cleared to before each application. + static struct gkyl_array* mkarr(bool on_gpu, long nc, long size) { @@ -26,8 +26,7 @@ mkarr(bool on_gpu, long nc, long size) static double filter_gain(int M, double fc, double freq) { - // Frequency response of the normalized Blackman-windowed sinc kernel - // at freq cycles/cell (real, since the kernel is symmetric). + // Frequency response of the kernel at freq cycles/cell. double wsum = 0.0, gain = 0.0; for (int k=-M; kmode_num*xn[0]); + fout[0] = 3.0; } -void eval_gauss_1x(double t, const double *xn, double *fout, void *ctx) +void eval_linear(double t, const double *xn, double *fout, void *ctx) { - fout[0] = exp(-pow((xn[0]-0.5)/0.04, 2)); + fout[0] = 1.5 + 0.5*xn[FILTER_DIR]; } -void eval_gauss_edge_1x(double t, const double *xn, double *fout, void *ctx) +static double +transverse_mod(const double *xn, int ndim) { - // Peaks at the lower boundary, so the stencil there is heavily reflected. - fout[0] = exp(-pow(xn[0]/0.04, 2)); + // Smooth modulation across the directions that are not filtered. + double mod = 1.0; + for (int d=0; dmode_num*xn[0]) - * (1.0 + 0.3*cos(2.0*M_PI*xn[1])) * (1.0 + 0.2*xn[2]); + struct profile_ctx *pctx = ctx; + fout[0] = cos(2.0*M_PI*pctx->mode_num*xn[FILTER_DIR])*transverse_mod(xn, pctx->ndim); } -static void -test_1x(bool use_gpu) +void eval_bump(double t, const double *xn, double *fout, void *ctx) { - int poly_order = 1; - int cells[] = {32}; - double lower[] = {0.0}, upper[] = {1.0}; - int nghost[] = {1}; - int M = 8; - double fc = 0.3; + // Off-center bump along the filtered direction. + struct profile_ctx *pctx = ctx; + fout[0] = (0.1 + exp(-pow((xn[FILTER_DIR]-pctx->x0)/pctx->w, 2)))*transverse_mod(xn, pctx->ndim); +} +struct filter_env { + bool use_gpu; struct gkyl_rect_grid grid; - gkyl_rect_grid_init(&grid, 1, lower, upper, cells); struct gkyl_basis basis; - gkyl_cart_modal_serendip(&basis, 1, poly_order); - struct gkyl_range local, local_ext; - gkyl_create_grid_ranges(&grid, nghost, &local_ext, &local); + struct gkyl_array *fin, *fout; // What the updater sees. + struct gkyl_array *fin_ho, *fout_ho; // Host copies the checks read. +}; - struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, M, - grid.dx[0]/fc, &basis, &grid, &local, use_gpu); +static void +filter_env_new(struct filter_env *env, bool use_gpu, int ndim, const int *cells, int poly_order) +{ + // Grid, basis, ranges and arrays for one test, on the unit cube. + double lower[GKYL_MAX_DIM], upper[GKYL_MAX_DIM]; + int nghost[GKYL_MAX_DIM]; + for (int d=0; dncomp, fin->size) : gkyl_array_acquire(fin); - struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); + env->use_gpu = use_gpu; + gkyl_rect_grid_init(&env->grid, ndim, lower, upper, cells); + gkyl_cart_modal_serendip(&env->basis, ndim, poly_order); + gkyl_create_grid_ranges(&env->grid, nghost, &env->local_ext, &env->local); - // a) A constant field is preserved exactly everywhere, including at - // the boundaries where the stencil is reflected. - gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, - poly_order+1, 1, eval_const_1x, NULL); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); - gkyl_proj_on_basis_release(proj); - gkyl_array_copy(fin, fin_ho); + env->fin = mkarr(use_gpu, env->basis.num_basis, env->local_ext.volume); + env->fout = mkarr(use_gpu, env->basis.num_basis, env->local_ext.volume); + env->fin_ho = use_gpu? mkarr(false, env->fin->ncomp, env->fin->size) : gkyl_array_acquire(env->fin); + env->fout_ho = use_gpu? mkarr(false, env->fout->ncomp, env->fout->size) : gkyl_array_acquire(env->fout); +} - gkyl_array_clear(fout, 7.0); - gkyl_dg_lowpass_filter_advance(lpf, fin, fout); - gkyl_array_copy(fout_ho, fout); +static void +filter_env_release(struct filter_env *env) +{ + gkyl_array_release(env->fin); + gkyl_array_release(env->fout); + gkyl_array_release(env->fin_ho); + gkyl_array_release(env->fout_ho); +} - struct gkyl_range_iter iter; - gkyl_range_iter_init(&iter, &local); - while (gkyl_range_iter_next(&iter)) { - long linidx = gkyl_range_idx(&local, iter.idx); - const double *in_c = gkyl_array_cfetch(fin_ho, linidx); - const double *out_c = gkyl_array_cfetch(fout_ho, linidx); - for (int c=0; cgrid, &env->basis, + env->basis.poly_order+1, 1, func, func_ctx); + gkyl_proj_on_basis_advance(proj, 0.0, &env->local, env->fin_ho); gkyl_proj_on_basis_release(proj); - gkyl_array_copy(fin, fin_ho); + gkyl_array_copy(env->fin, env->fin_ho); - gkyl_dg_lowpass_filter_advance(lpf, fin, fout); - gkyl_array_copy(fout_ho, fout); + gkyl_array_clear(env->fout, FOUT_FILL); + struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(FILTER_DIR, M, + cutoff_wavelength, &env->basis, &env->grid, sub, env->use_gpu); + gkyl_dg_lowpass_filter_advance(lpf, env->fin, env->fout); + gkyl_dg_lowpass_filter_release(lpf); + gkyl_array_copy(env->fout_ho, env->fout); +} - gkyl_range_iter_init(&iter, &local); +static double +filter_integral_check(struct filter_env *env, const struct gkyl_range *sub, + double tol, const char *what) +{ + // Change of the integral over sub, as a fraction of the mass in the range. + double tot_in = 0.0, tot_out = 0.0, mass = 0.0; + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, sub); while (gkyl_range_iter_next(&iter)) { - if (iter.idx[0] < local.lower[0]+M || iter.idx[0] > local.upper[0]-M) - continue; - long linidx = gkyl_range_idx(&local, iter.idx); - const double *in_c = gkyl_array_cfetch(fin_ho, linidx); - const double *out_c = gkyl_array_cfetch(fout_ho, linidx); - for (int c=0; c local.upper[0]-M) - continue; - long linidx = gkyl_range_idx(&local, iter.idx); - const double *in_c = gkyl_array_cfetch(fin_ho, linidx); - const double *out_c = gkyl_array_cfetch(fout_ho, linidx); - for (int c=0; c local.upper[0]-M) - continue; - const double *out_c = gkyl_array_cfetch(fout_ho, gkyl_range_idx(&local, iter.idx)); - for (int c=0; cfin_ho, linidx))[0]; + tot_in += in; + mass += fabs(in); + tot_out += ((const double *) gkyl_array_cfetch(env->fout_ho, linidx))[0]; } - gkyl_dg_lowpass_filter_release(lpf); - gkyl_array_release(fin); - gkyl_array_release(fout); - gkyl_array_release(fin_ho); - gkyl_array_release(fout_ho); + double rel = (tot_out-tot_in)/mass; + TEST_CHECK( fabs(rel) < tol ); + TEST_MSG("%s: the integral changed by %.3e of the mass in the range", what, rel); + return rel; } static void -test_1x_conservation(bool use_gpu) +filter_gain_check(struct filter_env *env, const struct gkyl_range *sub, int edge_skip, + double gain, double tol, const char *what) { - // The total integral is conserved everywhere: the kernel weights sum to 1 (reflected stencil at the boundaries). - int poly_order = 1; - int cells[] = {64}; - double lower[] = {0.0}, upper[] = {1.0}; - int nghost[] = {1}; - - struct gkyl_rect_grid grid; - gkyl_rect_grid_init(&grid, 1, lower, upper, cells); - struct gkyl_basis basis; - gkyl_cart_modal_serendip(&basis, 1, poly_order); + // Check fout = gain*fin, skipping edge_skip cells at each edge of sub. + struct gkyl_range_iter iter; + gkyl_range_iter_init(&iter, sub); + while (gkyl_range_iter_next(&iter)) { + if (iter.idx[FILTER_DIR] < sub->lower[FILTER_DIR]+edge_skip || + iter.idx[FILTER_DIR] > sub->upper[FILTER_DIR]-edge_skip) + continue; - struct gkyl_range local, local_ext; - gkyl_create_grid_ranges(&grid, nghost, &local_ext, &local); - - struct gkyl_array *fin = mkarr(use_gpu, basis.num_basis, local_ext.volume); - struct gkyl_array *fout = mkarr(use_gpu, basis.num_basis, local_ext.volume); - struct gkyl_array *fin_ho = use_gpu? mkarr(false, fin->ncomp, fin->size) : gkyl_array_acquire(fin); - struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); - - // A ramp peaks at one boundary, a boundary-hugging gaussian at the other. - evalf_t evals[] = {eval_linear_1x, eval_gauss_edge_1x, eval_gauss_1x}; - int half_widths[] = {8, 8, 80}; // The last stencil is wider than the grid. - - for (int q=0; q<3; q++) { - struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, half_widths[q], - grid.dx[0]/0.3, &basis, &grid, &local, use_gpu); - - gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, - poly_order+1, 1, evals[q], NULL); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); - gkyl_proj_on_basis_release(proj); - gkyl_array_copy(fin, fin_ho); - - gkyl_dg_lowpass_filter_advance(lpf, fin, fout); - gkyl_array_copy(fout_ho, fout); - - double tot_in = 0.0, tot_out = 0.0; - struct gkyl_range_iter iter; - gkyl_range_iter_init(&iter, &local); - while (gkyl_range_iter_next(&iter)) { - long linidx = gkyl_range_idx(&local, iter.idx); - tot_in += ((const double *) gkyl_array_cfetch(fin_ho, linidx))[0]; - tot_out += ((const double *) gkyl_array_cfetch(fout_ho, linidx))[0]; + long linidx = gkyl_range_idx(sub, iter.idx); + const double *in_c = gkyl_array_cfetch(env->fin_ho, linidx); + const double *out_c = gkyl_array_cfetch(env->fout_ho, linidx); + for (int c=0; cbasis.num_basis; c++) { + TEST_CHECK( fabs(out_c[c] - gain*in_c[c]) < tol ); + TEST_MSG("%s, cell %d coeff %d: expected %.13e, got %.13e", + what, iter.idx[FILTER_DIR], c, gain*in_c[c], out_c[c]); } - TEST_CHECK( fabs(tot_out-tot_in) < 1e-12*fabs(tot_in) ); - TEST_MSG("case %d (M=%d): total in %.13e | out %.13e | rel change %.3e", - q, half_widths[q], tot_in, tot_out, fabs(tot_out-tot_in)/fabs(tot_in)); - - gkyl_dg_lowpass_filter_release(lpf); } - - gkyl_array_release(fin); - gkyl_array_release(fout); - gkyl_array_release(fin_ho); - gkyl_array_release(fout_ho); } static void -test_3x(bool use_gpu) +test_response(bool use_gpu, int ndim, const int *cells) { - // Filter along x of a 3D field. For a separable field g(x)*h(y,z) the - // p=1 tensor coefficients factor too, so filtering in x scales the - // coefficients by the kernel's response at g's frequency, leaving the - // (y,z) dependence untouched. - int poly_order = 1; - int cells[] = {32, 8, 6}; - double lower[] = {0.0, 0.0, 0.0}, upper[] = {1.0, 1.0, 1.0}; - int nghost[] = {1, 1, 1}; + // Away from the edges the filter scales each field by the kernel response. int M = 8; double fc = 0.3; - struct gkyl_rect_grid grid; - gkyl_rect_grid_init(&grid, 3, lower, upper, cells); - struct gkyl_basis basis; - gkyl_cart_modal_serendip(&basis, 3, poly_order); - - struct gkyl_range local, local_ext; - gkyl_create_grid_ranges(&grid, nghost, &local_ext, &local); - - struct gkyl_dg_lowpass_filter *lpf = gkyl_dg_lowpass_filter_new(0, M, - grid.dx[0]/fc, &basis, &grid, &local, use_gpu); - - struct gkyl_array *fin = mkarr(use_gpu, basis.num_basis, local_ext.volume); - struct gkyl_array *fout = mkarr(use_gpu, basis.num_basis, local_ext.volume); - struct gkyl_array *fin_ho = use_gpu? mkarr(false, fin->ncomp, fin->size) : gkyl_array_acquire(fin); - struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); - - struct mode_ctx mctx = { .mode_num = 16.0 }; // x-Nyquist mode for 32 cells. - gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, - poly_order+1, 1, eval_mode_3x, &mctx); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); - gkyl_proj_on_basis_release(proj); - gkyl_array_copy(fin, fin_ho); - - gkyl_dg_lowpass_filter_advance(lpf, fin, fout); - gkyl_array_copy(fout_ho, fout); - - double freq = mctx.mode_num/cells[0]; // Cycles/cell. - double gain = filter_gain(M, fc, freq); + struct filter_env env; + filter_env_new(&env, use_gpu, ndim, cells, 1); + double cutoff = env.grid.dx[FILTER_DIR]/fc; + + double mod_max = 1.0; // Peak of the transverse modulation. + for (int d=0; d local.upper[0]-M) + if (iter.idx[FILTER_DIR] == env.local.upper[FILTER_DIR]) continue; - long linidx = gkyl_range_idx(&local, iter.idx); - const double *in_c = gkyl_array_cfetch(fin_ho, linidx); - const double *out_c = gkyl_array_cfetch(fout_ho, linidx); - for (int c=0; cncomp, fin->size) : gkyl_array_acquire(fin); - struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); - gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, - poly_order+1, 1, eval_linear_1x, NULL); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); - gkyl_proj_on_basis_release(proj); - gkyl_array_copy(fin, fin_ho); - - gkyl_dg_lowpass_filter_advance(lpf, fin, fout); - gkyl_array_copy(fout_ho, fout); + int idx_up[GKYL_MAX_DIM]; + for (int d=0; dncomp, fin->size) : gkyl_array_acquire(fin); - struct gkyl_array *fout_ho = use_gpu? mkarr(false, fout->ncomp, fout->size) : gkyl_array_acquire(fout); + struct gkyl_range *ranges[] = {&env.local, &cut}; + const char *const_names[] = {"reflected stencil, constant", "truncated stencil, constant"}; + const char *bump_names[] = {"reflected stencil, bump", "truncated stencil, bump"}; - gkyl_proj_on_basis *proj = gkyl_proj_on_basis_new(&grid, &basis, - poly_order+1, 1, eval_const_1x, NULL); - gkyl_proj_on_basis_advance(proj, 0.0, &local, fin_ho); - gkyl_proj_on_basis_release(proj); - gkyl_array_copy(fin, fin_ho); + // A bump next to the truncated edge, so the profile overlaps the cut rows. + struct profile_ctx bump = { .ndim = ndim, .x0 = 0.68, .w = 0.05 }; - gkyl_dg_lowpass_filter_advance(lpf, fin, fout); - gkyl_array_copy(fout_ho, fout); + for (int r=0; r<2; r++) { + filter_apply(&env, ranges[r], M, cutoff, eval_const, NULL); + filter_integral_check(&env, ranges[r], 1e-12, const_names[r]); - struct gkyl_range_iter iter; - gkyl_range_iter_init(&iter, &sub); - while (gkyl_range_iter_next(&iter)) { - long linidx = gkyl_range_idx(&sub, iter.idx); - const double *in_c = gkyl_array_cfetch(fin_ho, linidx); - const double *out_c = gkyl_array_cfetch(fout_ho, linidx); - for (int c=0; c Date: Tue, 25 Aug 2026 16:53:48 +0200 Subject: [PATCH 25/30] adapt the resolution of the 3x2v tcv adapt source reg test so that it runs faster --- gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c index c8d4ac4546..b7be922bbf 100644 --- a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c @@ -439,11 +439,11 @@ struct gk_app_ctx create_ctx(void) double floor_srcRECY = 1e-10; // Grid parameters (reduced resolution for the regression test, minimal recommended values in comments) - int Nx = 9; // (24) The LCFS is positionned at 1/3 of the domain -> the resolution must be divisible by 3. - int Ny = 4; // (16) + int Nx = 15; // (24) The LCFS is positionned at 1/3 of the domain -> the resolution must be divisible by 3. + int Ny = 8; // (16) int Nz = 8; // (12) - int Nvpar = 8; // (12) - int Nmu = 8; // (8) + int Nvpar = 4; // (12) + int Nmu = 4; // (8) int poly_order = 1; // Velocity box dimensions double vpar_max_elc = 5.*vte; From 6d5ad963f3ce281a6e5f1de97b12270b9c11e817 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Tue, 25 Aug 2026 16:56:23 +0200 Subject: [PATCH 26/30] add the filter parameters in the regression test of TCV 3x2v --- gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c index b7be922bbf..5a7e35448e 100644 --- a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c @@ -832,6 +832,9 @@ main(int argc, char **argv) .parallel_upper_bc_shift_func = bc_shift_func_up, .parallel_lower_bc_shift_ctx = &ctx, .parallel_upper_bc_shift_ctx = &ctx, + .ts_filter_cutoff_wavelength = 2.0*ctx.Nx/ctx.Lx, + .ts_filter_half_width = 1, + .ts_upsample_factor = 2, }; // Parallelism From 19c21dc38d925ca2591e31b2df01fa2e2a0e23d5 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Tue, 25 Aug 2026 17:29:32 +0200 Subject: [PATCH 27/30] fix typo in the cutoff wavelength --- gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c index 5a7e35448e..48c6a406bf 100644 --- a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c @@ -832,7 +832,7 @@ main(int argc, char **argv) .parallel_upper_bc_shift_func = bc_shift_func_up, .parallel_lower_bc_shift_ctx = &ctx, .parallel_upper_bc_shift_ctx = &ctx, - .ts_filter_cutoff_wavelength = 2.0*ctx.Nx/ctx.Lx, + .ts_filter_cutoff_wavelength = 2.0*ctx.Lx/ctx.Nx, .ts_filter_half_width = 1, .ts_upsample_factor = 2, }; From 63dee825c1181e518ba78891474d845e9994d736 Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Tue, 1 Sep 2026 08:24:08 -0400 Subject: [PATCH 28/30] adopt the "inew" and "new" updater creator naming convention and remove the conservation test for the bump when the stencil is truncated because it cannot conserve in this scenario. --- core/unit/ctest_dg_lowpass_filter.c | 8 ++-- gyrokinetic/apps/gyrokinetic.c | 8 ++-- gyrokinetic/unit/ctest_bc_twistshift.c | 6 +-- gyrokinetic/unit/ctest_twistshift_dg.c | 52 +++++++++++------------ gyrokinetic/zero/bc_twistshift.c | 45 ++++++++++++++++---- gyrokinetic/zero/gkyl_bc_twistshift.h | 28 +++++++++++++ gyrokinetic/zero/gkyl_twistshift_dg.h | 34 ++++++++++++--- gyrokinetic/zero/twistshift_dg.c | 57 ++++++++++++++++++-------- 8 files changed, 172 insertions(+), 66 deletions(-) diff --git a/core/unit/ctest_dg_lowpass_filter.c b/core/unit/ctest_dg_lowpass_filter.c index dc91b3738b..ac951f7efe 100644 --- a/core/unit/ctest_dg_lowpass_filter.c +++ b/core/unit/ctest_dg_lowpass_filter.c @@ -283,7 +283,8 @@ test_conservation(bool use_gpu, int ndim, const int *cells) struct gkyl_range *ranges[] = {&env.local, &cut}; const char *const_names[] = {"reflected stencil, constant", "truncated stencil, constant"}; - const char *bump_names[] = {"reflected stencil, bump", "truncated stencil, bump"}; + // The truncated stencil with non constant field does not conserve the particle number. + const char *bump_names[] = {"reflected stencil, bump"};//, "truncated stencil, bump"}; // A bump next to the truncated edge, so the profile overlaps the cut rows. struct profile_ctx bump = { .ndim = ndim, .x0 = 0.68, .w = 0.05 }; @@ -291,10 +292,9 @@ test_conservation(bool use_gpu, int ndim, const int *cells) for (int r=0; r<2; r++) { filter_apply(&env, ranges[r], M, cutoff, eval_const, NULL); filter_integral_check(&env, ranges[r], 1e-12, const_names[r]); - - filter_apply(&env, ranges[r], M, cutoff, eval_bump, &bump); - filter_integral_check(&env, ranges[r], 1e-12, bump_names[r]); } + filter_apply(&env, ranges[0], M, cutoff, eval_bump, &bump); + filter_integral_check(&env, ranges[0], 1e-12, bump_names[0]); filter_env_release(&env); } diff --git a/gyrokinetic/apps/gyrokinetic.c b/gyrokinetic/apps/gyrokinetic.c index 84b6c40550..377a82059b 100644 --- a/gyrokinetic/apps/gyrokinetic.c +++ b/gyrokinetic/apps/gyrokinetic.c @@ -1254,15 +1254,15 @@ gyrokinetic_app_write_ts_shift_mapc2p(struct gkyl_gyrokinetic_app *app) .shear_dir = 0, // shift varies with x. .edge = eI == 0? GKYL_LOWER_EDGE : GKYL_UPPER_EDGE, .cdim = app->cdim, - .bcdir_ext_update_r = app->global_par_ext, + .bcdir_ext_update_r = &app->global_par_ext, .num_ghost = ghost, // one ghost per config direction - .basis = app->basis, - .grid = app->grid, + .basis = &app->basis, + .grid = &app->grid, .shift_func = eI == 0? app->gk_geom->parallel_lower_bc_shift_func : app->gk_geom->parallel_upper_bc_shift_func, .shift_func_ctx = eI == 0? app->gk_geom->parallel_lower_bc_shift_ctx : app->gk_geom->parallel_upper_bc_shift_ctx, .use_gpu = app->use_gpu, }; - struct gkyl_twistshift_dg *bc_ts_op = gkyl_twistshift_dg_new(&ts_inp); + struct gkyl_twistshift_dg *bc_ts_op = gkyl_twistshift_dg_inew(&ts_inp); struct gkyl_array *delta_ts_x = eI == 0? app->delta_ts_x_lo : app->delta_ts_x_up; delta_ts_x = gkyl_twistshift_dg_get_shift_objects(bc_ts_op, diff --git a/gyrokinetic/unit/ctest_bc_twistshift.c b/gyrokinetic/unit/ctest_bc_twistshift.c index dc75e02776..05a9536d3a 100644 --- a/gyrokinetic/unit/ctest_bc_twistshift.c +++ b/gyrokinetic/unit/ctest_bc_twistshift.c @@ -219,11 +219,11 @@ test_plain_matches_twistshift_dg(void) struct gkyl_array *f_ref = ts_donor_new(&s); struct gkyl_twistshift_dg_inp tsinp = { .bc_dir = ts_bc_dir, .shift_dir = 1, .shear_dir = 0, .edge = edge, - .cdim = ts_cdim, .bcdir_ext_update_r = s.update_r, .num_ghost = s.ghost, - .basis = s.basis, .grid = s.grid, + .cdim = ts_cdim, .bcdir_ext_update_r = &s.update_r, .num_ghost = s.ghost, + .basis = &s.basis, .grid = &s.grid, .shift_func = shift_func, .shift_func_ctx = &tctx, .use_gpu = false, }; - struct gkyl_twistshift_dg *ts = gkyl_twistshift_dg_new(&tsinp); + struct gkyl_twistshift_dg *ts = gkyl_twistshift_dg_inew(&tsinp); gkyl_twistshift_dg_advance(ts, f_ref, f_ref); gkyl_twistshift_dg_release(ts); diff --git a/gyrokinetic/unit/ctest_twistshift_dg.c b/gyrokinetic/unit/ctest_twistshift_dg.c index 6c8b50e1d6..ae00cdb2cd 100644 --- a/gyrokinetic/unit/ctest_twistshift_dg.c +++ b/gyrokinetic/unit/ctest_twistshift_dg.c @@ -335,17 +335,17 @@ test_bc_twistshift_3x_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, .shear_dir = 0, // shift varies with x. .edge = edge, .cdim = cdim, - .bcdir_ext_update_r = update_rng, + .bcdir_ext_update_r = &update_rng, .num_ghost = ghost, - .basis = basis, - .grid = grid, + .basis = &basis, + .grid = &grid, .shift_func = shift1_fig6, // .shift_func = shift_fig9, .shift_func_ctx = &proj_ctx, .use_gpu = use_gpu, }; - struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_inew(&tsinp); // First apply periodicity in z. struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); @@ -402,7 +402,7 @@ test_bc_twistshift_3x_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_array_copy_range_to_range(distf, distf, &skin_rng, &ghost_rng); tsinp.shift_func = shift1m_fig6; // tsinp.shift_func = shiftm_fig9; - struct gkyl_twistshift_dg *tsup_m = gkyl_twistshift_dg_new(&tsinp); + struct gkyl_twistshift_dg *tsup_m = gkyl_twistshift_dg_inew(&tsinp); gkyl_twistshift_dg_advance(tsup_m, distf, distf); gkyl_array_copy(distf_ho, distf); @@ -592,17 +592,17 @@ test_bc_twistshift_3x2v_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, .shear_dir = 0, // shift varies with x. .edge = edge, .cdim = cdim, - .bcdir_ext_update_r = update_rng, + .bcdir_ext_update_r = &update_rng, .num_ghost = ghost, - .basis = basis, - .grid = grid, + .basis = &basis, + .grid = &grid, .shift_func = shift1_fig6, // .shift_func = shift_fig9, .shift_func_ctx = &proj_ctx, .use_gpu = use_gpu, }; - struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_inew(&tsinp); // First apply periodicity in z. struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); @@ -743,7 +743,7 @@ test_bc_twistshift_3x2v_fig6_wcells(const int *cells, enum gkyl_edge_loc edge, gkyl_array_copy_range_to_range(distf, distf, &skin_rng, &ghost_rng); tsinp.shift_func = shift1m_fig6; // tsinp.shift_func = shiftm_fig9; - struct gkyl_twistshift_dg *tsup_m = gkyl_twistshift_dg_new(&tsinp); + struct gkyl_twistshift_dg *tsup_m = gkyl_twistshift_dg_inew(&tsinp); gkyl_twistshift_dg_advance(tsup_m, distf, distf); gkyl_array_copy(distf_ho, distf); @@ -1003,16 +1003,16 @@ test_bc_twistshift_3x_fig11_wcells(const int *cells, enum gkyl_edge_loc edge, .shear_dir = 0, // shift varies with x. .edge = edge, .cdim = cdim, - .bcdir_ext_update_r = update_rng, + .bcdir_ext_update_r = &update_rng, .num_ghost = ghost, - .basis = basis, - .grid = grid, + .basis = &basis, + .grid = &grid, .shift_func = shift_fig11, .shift_func_ctx = &proj_ctx, .use_gpu = use_gpu, }; - struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_inew(&tsinp); // First apply periodicity in z. struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); @@ -1370,16 +1370,16 @@ test_bc_twistshift_3x_fig14_wcells(const int *cells, enum gkyl_edge_loc edge, .shear_dir = 0, // shift varies with x. .edge = edge, .cdim = cdim, - .bcdir_ext_update_r = update_rng, + .bcdir_ext_update_r = &update_rng, .num_ghost = ghost, - .basis = basis, - .grid = grid, + .basis = &basis, + .grid = &grid, .shift_func = shift_fig14, .shift_func_ctx = &proj_ctx, .use_gpu = use_gpu, }; - struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_inew(&tsinp); // First apply periodicity in z. struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); @@ -1820,16 +1820,16 @@ test_bc_twistshift_3x2v_fig11_wcells(const int *cells, enum gkyl_edge_loc edge, .shear_dir = 0, // shift varies with x. .edge = edge, .cdim = cdim, - .bcdir_ext_update_r = update_rng, + .bcdir_ext_update_r = &update_rng, .num_ghost = ghost, - .basis = basis, - .grid = grid, + .basis = &basis, + .grid = &grid, .shift_func = shift_fig11, .shift_func_ctx = &proj_ctx, .use_gpu = use_gpu, }; - struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_inew(&tsinp); // First apply periodicity in z. struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); @@ -2407,16 +2407,16 @@ test_bc_twistshift_3x_cbc_wcells(const int *cells, enum gkyl_edge_loc edge, .shear_dir = 0, .edge = edge, .cdim = cdim, - .bcdir_ext_update_r = update_rng, + .bcdir_ext_update_r = &update_rng, .num_ghost = ghost, - .basis = basis, - .grid = grid, + .basis = &basis, + .grid = &grid, .shift_func = (edge == GKYL_LOWER_EDGE) ? bc_shift_func_lo_cbc : bc_shift_func_up_cbc, .shift_func_ctx = &app_ctx, .use_gpu = use_gpu, }; - struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_new(&tsinp); + struct gkyl_twistshift_dg *tsup = gkyl_twistshift_dg_inew(&tsinp); struct gkyl_array *buff_per = mkarr(use_gpu, basis.num_basis, skin_rng.volume); apply_periodic_bc(buff_per, distf, bc_dir, skin_ghost); diff --git a/gyrokinetic/zero/bc_twistshift.c b/gyrokinetic/zero/bc_twistshift.c index 40c475d8fc..e6d61c980a 100644 --- a/gyrokinetic/zero/bc_twistshift.c +++ b/gyrokinetic/zero/bc_twistshift.c @@ -125,17 +125,17 @@ gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp) .shear_dir = inp->shear_dir, .edge = inp->edge, .cdim = inp->cdim, - .bcdir_ext_update_r = *inp->bcdir_ext_update_r, + .bcdir_ext_update_r = inp->bcdir_ext_update_r, .num_ghost = inp->num_ghost, - .basis = *inp->basis, - .grid = *inp->grid, + .basis = inp->basis, + .grid = inp->grid, .shift_func = inp->shift_func, .shift_func_ctx = inp->shift_func_ctx, .shift_dg = inp->shift_dg, .use_gpu = inp->use_gpu, .shift_poly_order = inp->shift_poly_order, }; - up->ts = gkyl_twistshift_dg_new(&tsinp); + up->ts = gkyl_twistshift_dg_inew(&tsinp); up->advance_func = bc_twistshift_advance_ts; return up; } @@ -206,22 +206,51 @@ gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp) .shear_dir = inp->shear_dir, .edge = inp->edge, .cdim = inp->cdim, - .bcdir_ext_update_r = up->ts_update_r, + .bcdir_ext_update_r = &up->ts_update_r, .num_ghost = inp->num_ghost, - .basis = *inp->basis, - .grid = up->ts_grid, + .basis = inp->basis, + .grid = &up->ts_grid, .shift_func = inp->shift_func, .shift_func_ctx = inp->shift_func_ctx, .shift_dg = shift_dg, .use_gpu = inp->use_gpu, .shift_poly_order = inp->shift_poly_order, }; - up->ts = gkyl_twistshift_dg_new(&tsinp); + up->ts = gkyl_twistshift_dg_inew(&tsinp); up->advance_func = bc_twistshift_advance_ts_filtered; return up; } +struct gkyl_bc_twistshift* +gkyl_bc_twistshift_new(int bc_dir, int shift_dir, int shear_dir, + enum gkyl_edge_loc edge, int cdim, const struct gkyl_range *bcdir_ext_update_r, const int *num_ghost, + const struct gkyl_basis *basis, const struct gkyl_rect_grid *grid, evalf_t shift_func, void *shift_func_ctx, + struct gkyl_array *shift_dg, int shift_poly_order, int filter_half_width, + double filter_cutoff_wavelength, int upsample_factor, bool use_gpu) +{ + struct gkyl_bc_twistshift_inp inp = { + .bc_dir = bc_dir , + .shift_dir = shift_dir , + .shear_dir = shear_dir , + .edge = edge , + .cdim = cdim , + .bcdir_ext_update_r = bcdir_ext_update_r , + .num_ghost = num_ghost , + .basis = basis , + .grid = grid , + .shift_func = shift_func , + .shift_func_ctx = shift_func_ctx , + .shift_dg = shift_dg , + .use_gpu = use_gpu , + .shift_poly_order = shift_poly_order , + .filter_half_width = filter_half_width , + .filter_cutoff_wavelength = filter_cutoff_wavelength, + .upsample_factor = upsample_factor , + }; + return gkyl_bc_twistshift_inew(&inp); +} + void gkyl_bc_twistshift_advance(struct gkyl_bc_twistshift *up, struct gkyl_array *fdo, struct gkyl_array *ftar) { diff --git a/gyrokinetic/zero/gkyl_bc_twistshift.h b/gyrokinetic/zero/gkyl_bc_twistshift.h index ac5dfefe1d..73c7952d99 100644 --- a/gyrokinetic/zero/gkyl_bc_twistshift.h +++ b/gyrokinetic/zero/gkyl_bc_twistshift.h @@ -42,6 +42,34 @@ struct gkyl_bc_twistshift_inp { */ struct gkyl_bc_twistshift* gkyl_bc_twistshift_inew(const struct gkyl_bc_twistshift_inp *inp); +/** + * Create a new updater to apply twist-shift BCs, passing each argument separately. + * + * @param bc_dir Direction in which to apply this BC. + * @param shift_dir Direction of the shift. + * @param shear_dir Direction in which the shift varies (shear). + * @param edge Edge to apply this BC at (lower/upper). + * @param cdim Configuration space dimensions. + * @param bcdir_ext_update_r Local range where to apply BC, extended in bc_dir. + * @param num_ghost Number of ghost cells in each direction. + * @param basis Basis of the field shifted. + * @param grid Grid the field shifted is defined on. + * @param shift_func Function defining the shift. + * @param shift_func_ctx Context for shift_func. + * @param shift_dg Discretized shift. + * @param shift_poly_order Basis order for the DG representation of the shift (optional). + * @param filter_half_width Filter stencil half-width M in cells of grid (0 = no filter). + * @param filter_cutoff_wavelength Filter cutoff wavelength (physical units). + * @param upsample_factor Supersampling factor along shear_dir (0/1 = none). + * @param use_gpu Whether to apply the BC using the GPU. + * @return New updater pointer. + */ +struct gkyl_bc_twistshift* gkyl_bc_twistshift_new(int bc_dir, int shift_dir, int shear_dir, + enum gkyl_edge_loc edge, int cdim, const struct gkyl_range *bcdir_ext_update_r, const int *num_ghost, + const struct gkyl_basis *basis, const struct gkyl_rect_grid *grid, evalf_t shift_func, void *shift_func_ctx, + struct gkyl_array *shift_dg, int shift_poly_order, int filter_half_width, + double filter_cutoff_wavelength, int upsample_factor, bool use_gpu); + /** * Apply the twist-shift periodic BC. Expects periodicity along bc_dir to have * been applied to the donor field beforehand. Can be used in-place. diff --git a/gyrokinetic/zero/gkyl_twistshift_dg.h b/gyrokinetic/zero/gkyl_twistshift_dg.h index 13028f5324..084bad037d 100644 --- a/gyrokinetic/zero/gkyl_twistshift_dg.h +++ b/gyrokinetic/zero/gkyl_twistshift_dg.h @@ -16,10 +16,10 @@ struct gkyl_twistshift_dg_inp { int shear_dir; // Direction in which the shift varies (shear). enum gkyl_edge_loc edge; // Edge of to apply this BC at (lower/upper). int cdim; // Configuration space dimensions. - struct gkyl_range bcdir_ext_update_r; // Local range where to apply BC, extended in bc_dir. + const struct gkyl_range *bcdir_ext_update_r; // Local range where to apply BC, extended in bc_dir. const int *num_ghost; // Number of ghost cells in each direction. - struct gkyl_basis basis; // Basis of the field shifted. - struct gkyl_rect_grid grid; // Grid the field shifted is defined on. + const struct gkyl_basis *basis; // Basis of the field shifted. + const struct gkyl_rect_grid *grid; // Grid the field shifted is defined on. evalf_t shift_func; // Function defining the shift. void *shift_func_ctx; // Context for shift_func. struct gkyl_array *shift_dg; // Discretized shift. @@ -34,8 +34,32 @@ struct gkyl_twistshift_dg_inp { * @param inp twistshift_dg_inp struct containing the inputs to the updater. * @return New updater pointer. */ -struct gkyl_twistshift_dg* gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp); - +struct gkyl_twistshift_dg* gkyl_twistshift_dg_inew(const struct gkyl_twistshift_dg_inp *inp); + +/** + * Create a new updater to apply twist-shift BCs, passing each argument separately. + * + * @param bc_dir Direction in which to apply this BC. + * @param shift_dir Direction of the shift. + * @param shear_dir Direction in which the shift varies (shear). + * @param edge Edge to apply this BC at (lower/upper). + * @param cdim Configuration space dimensions. + * @param bcdir_ext_update_r Local range where to apply BC, extended in bc_dir. + * @param num_ghost Number of ghost cells in each direction. + * @param basis Basis of the field shifted. + * @param grid Grid the field shifted is defined on. + * @param shift_func Function defining the shift. + * @param shift_func_ctx Context for shift_func. + * @param shift_dg Discretized shift. + * @param shift_poly_order Basis order for the DG representation of the shift (optional). + * @param use_gpu Whether to apply the BC using the GPU. + * @return New updater pointer. + */ +struct gkyl_twistshift_dg* gkyl_twistshift_dg_new(int bc_dir, int shift_dir, int shear_dir, + enum gkyl_edge_loc edge, int cdim, const struct gkyl_range *bcdir_ext_update_r, const int *num_ghost, + const struct gkyl_basis *basis, const struct gkyl_rect_grid *grid, evalf_t shift_func, void *shift_func_ctx, + struct gkyl_array *shift_dg, int shift_poly_order, bool use_gpu); + /** * Apply the twist-shift. It assumes that periodicity along bc_dir has been * applied to the donor field. Can be used in-place. diff --git a/gyrokinetic/zero/twistshift_dg.c b/gyrokinetic/zero/twistshift_dg.c index 0c1baa11ce..fc16383692 100644 --- a/gyrokinetic/zero/twistshift_dg.c +++ b/gyrokinetic/zero/twistshift_dg.c @@ -1703,7 +1703,7 @@ gkyl_twistshift_dg_choose_kernels(struct gkyl_basis basis, int cdim, int shift_p } struct gkyl_twistshift_dg* -gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp) +gkyl_twistshift_dg_inew(const struct gkyl_twistshift_dg_inp *inp) { // Allocate space for new updater. @@ -1713,18 +1713,18 @@ gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp) up->shift_dir = inp->shift_dir; up->shear_dir = inp->shear_dir; up->edge = inp->edge; - up->basis = inp->basis; - up->grid = inp->grid; + up->basis = *inp->basis; + up->grid = *inp->grid; up->use_gpu = inp->use_gpu; - up->local_bcdir_ext_r = inp->bcdir_ext_update_r; + up->local_bcdir_ext_r = *inp->bcdir_ext_update_r; // Assume the poly order of the DG shift is the same as that of the field, // unless requested otherwise. - up->shift_poly_order = inp->basis.poly_order; + up->shift_poly_order = inp->basis->poly_order; if (inp->shift_poly_order) up->shift_poly_order = inp->shift_poly_order; - const int ndim = inp->bcdir_ext_update_r.ndim; + const int ndim = inp->bcdir_ext_update_r->ndim; // Check that it is being used for 3D or 5D. Likely only small changes are // needed to make it work in other dimensions. assert(ndim == 3 || ndim == 5); @@ -1734,9 +1734,9 @@ gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp) // Create 1D grid and range in the direction of the shear. gkyl_range_init(&up->shear_r, 1, (int[]) {up->local_bcdir_ext_r.lower[inp->shear_dir]}, (int[]) {up->local_bcdir_ext_r.upper[inp->shear_dir]}); - lo1d[0] = inp->grid.lower[up->shear_dir]; - up1d[0] = inp->grid.upper[up->shear_dir]; - cells1d[0] = inp->grid.cells[up->shear_dir]; + lo1d[0] = inp->grid->lower[up->shear_dir]; + up1d[0] = inp->grid->upper[up->shear_dir]; + cells1d[0] = inp->grid->cells[up->shear_dir]; gkyl_rect_grid_init(&up->shear_grid, 1, lo1d, up1d, cells1d); int idx[] = {up->shear_r.lower[0]}; long linidx = gkyl_range_idx(&up->shear_r, idx); @@ -1744,9 +1744,9 @@ gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp) // Create 1D grid and range in the diretion of the shift. gkyl_range_init(&up->shift_r, 1, (int[]) {up->local_bcdir_ext_r.lower[inp->shift_dir]}, (int[]) {up->local_bcdir_ext_r.upper[inp->shift_dir]}); - lo1d[0] = inp->grid.lower[up->shift_dir]; - up1d[0] = inp->grid.upper[up->shift_dir]; - cells1d[0] = inp->grid.cells[up->shift_dir]; + lo1d[0] = inp->grid->lower[up->shift_dir]; + up1d[0] = inp->grid->upper[up->shift_dir]; + cells1d[0] = inp->grid->cells[up->shift_dir]; gkyl_rect_grid_init(&up->shift_grid, 1, lo1d, up1d, cells1d); // Create 2D grid (and range) the twist-shift takes place in. @@ -1765,9 +1765,9 @@ gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp) } gkyl_range_init(&up->ts_r, 2, (int[]) {up->local_bcdir_ext_r.lower[dimlo], up->local_bcdir_ext_r.lower[dimup]}, (int[]) {up->local_bcdir_ext_r.upper[dimlo], up->local_bcdir_ext_r.upper[dimup]}); - double lo2d[] = {inp->grid.lower[dimlo], inp->grid.lower[dimup]}; - double up2d[] = {inp->grid.upper[dimlo], inp->grid.upper[dimup]}; - int cells2d[] = {inp->grid.cells[dimlo], inp->grid.cells[dimup]}; + double lo2d[] = {inp->grid->lower[dimlo], inp->grid->lower[dimup]}; + double up2d[] = {inp->grid->upper[dimlo], inp->grid->upper[dimup]}; + int cells2d[] = {inp->grid->cells[dimlo], inp->grid->cells[dimup]}; gkyl_rect_grid_init(&up->ts_grid, 2, lo2d, up2d, cells2d); // Project the shift onto the shift basis. @@ -1830,7 +1830,7 @@ gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp) // Choose the kernels that do the subcell and full cell integrals up->kernels = gkyl_malloc(sizeof(struct gkyl_twistshift_dg_kernels)); - gkyl_twistshift_dg_choose_kernels(inp->basis, inp->cdim, up->shift_poly_order, up->kernels); + gkyl_twistshift_dg_choose_kernels(*inp->basis, inp->cdim, up->shift_poly_order, up->kernels); // The BC is applied as a set of matrix-matrix multiplications // f_i = sum_{q}^{N_do(i)} A_q,i B_q,i @@ -1900,6 +1900,31 @@ gkyl_twistshift_dg_new(const struct gkyl_twistshift_dg_inp *inp) return up; } +struct gkyl_twistshift_dg* +gkyl_twistshift_dg_new(int bc_dir, int shift_dir, int shear_dir, + enum gkyl_edge_loc edge, int cdim, const struct gkyl_range *bcdir_ext_update_r, const int *num_ghost, + const struct gkyl_basis *basis, const struct gkyl_rect_grid *grid, evalf_t shift_func, void *shift_func_ctx, + struct gkyl_array *shift_dg, int shift_poly_order, bool use_gpu) +{ + struct gkyl_twistshift_dg_inp inp = { + .bc_dir = bc_dir , + .shift_dir = shift_dir , + .shear_dir = shear_dir , + .edge = edge , + .cdim = cdim , + .bcdir_ext_update_r = bcdir_ext_update_r, + .num_ghost = num_ghost , + .basis = basis , + .grid = grid , + .shift_func = shift_func , + .shift_func_ctx = shift_func_ctx , + .shift_dg = shift_dg , + .use_gpu = use_gpu , + .shift_poly_order = shift_poly_order , + }; + return gkyl_twistshift_dg_inew(&inp); +} + void gkyl_twistshift_dg_advance(struct gkyl_twistshift_dg *up, struct gkyl_array *fdo, struct gkyl_array *ftar) { From 8a69bbf5342d59dd50c01e4cfcdc21bea5d703cf Mon Sep 17 00:00:00 2001 From: antoinehoff Date: Wed, 2 Sep 2026 09:37:30 -0400 Subject: [PATCH 29/30] fix the cell translation unit test of tsbc --- gyrokinetic/unit/ctest_bc_twistshift.c | 31 ++++++++------------------ 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/gyrokinetic/unit/ctest_bc_twistshift.c b/gyrokinetic/unit/ctest_bc_twistshift.c index 60ced3b492..05a9536d3a 100644 --- a/gyrokinetic/unit/ctest_bc_twistshift.c +++ b/gyrokinetic/unit/ctest_bc_twistshift.c @@ -463,28 +463,15 @@ test_zero_shear_is_cell_translation(void) struct gkyl_range_iter iter; gkyl_range_iter_init(&iter, &s.ghost_r); while (gkyl_range_iter_next(&iter)) { - for (int i=0; i Date: Wed, 2 Sep 2026 09:38:51 -0400 Subject: [PATCH 30/30] small typo correction --- gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c index 48c6a406bf..1a9394d9b3 100644 --- a/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c +++ b/gyrokinetic/creg/rt_gk_tcv_iwl_adapt_source_3x2v_p1.c @@ -822,7 +822,7 @@ main(int argc, char **argv) struct gkyl_gyrokinetic_geometry geometry = { .geometry_id = GKYL_GEOMETRY_MAPC2P, .world = {0.}, - .mapc2p = mapc2p, // mapping of cCOREutational to physical space + .mapc2p = mapc2p, // mapping of computational to physical space .c2p_ctx = &ctx, .bfield_func = bfield_func, // magnetic field .bfield_ctx = &ctx,