1*7e3656bdSJames Wright // SPDX-FileCopyrightText: Copyright (c) 2017-2025, HONEE contributors. 2*7e3656bdSJames Wright // SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause 3*7e3656bdSJames Wright 4*7e3656bdSJames Wright #include <honee.h> 5*7e3656bdSJames Wright #include <petscdmplex.h> 6*7e3656bdSJames Wright 7*7e3656bdSJames Wright #ifndef M_PI 8*7e3656bdSJames Wright #define M_PI 3.14159265358979323846 9*7e3656bdSJames Wright #endif 10*7e3656bdSJames Wright 11*7e3656bdSJames Wright static PetscErrorCode GetYNodeLocs(const MPI_Comm comm, const char path[PETSC_MAX_PATH_LEN], PetscReal **pynodes, PetscInt *nynodes) { 12*7e3656bdSJames Wright int ndims, dims[2] = {0.}; 13*7e3656bdSJames Wright FILE *fp; 14*7e3656bdSJames Wright const PetscInt char_array_len = 512; 15*7e3656bdSJames Wright char line[char_array_len]; 16*7e3656bdSJames Wright PetscReal *node_locs; 17*7e3656bdSJames Wright 18*7e3656bdSJames Wright PetscFunctionBeginUser; 19*7e3656bdSJames Wright PetscCall(PetscFOpen(comm, path, "r", &fp)); 20*7e3656bdSJames Wright PetscCall(PetscSynchronizedFGets(comm, fp, char_array_len, line)); 21*7e3656bdSJames Wright { 22*7e3656bdSJames Wright char **array; 23*7e3656bdSJames Wright 24*7e3656bdSJames Wright PetscCall(PetscStrToArray(line, ' ', &ndims, &array)); 25*7e3656bdSJames Wright for (PetscInt i = 0; i < ndims; i++) dims[i] = atoi(array[i]); 26*7e3656bdSJames Wright PetscCall(PetscStrToArrayDestroy(ndims, array)); 27*7e3656bdSJames Wright } 28*7e3656bdSJames Wright if (ndims < 2) dims[1] = 1; // Assume 1 column of data is not otherwise specified 29*7e3656bdSJames Wright *nynodes = dims[0]; 30*7e3656bdSJames Wright PetscCall(PetscMalloc1(*nynodes, &node_locs)); 31*7e3656bdSJames Wright 32*7e3656bdSJames Wright for (PetscInt i = 0; i < dims[0]; i++) { 33*7e3656bdSJames Wright char **array; 34*7e3656bdSJames Wright 35*7e3656bdSJames Wright PetscCall(PetscSynchronizedFGets(comm, fp, char_array_len, line)); 36*7e3656bdSJames Wright PetscCall(PetscStrToArray(line, ' ', &ndims, &array)); 37*7e3656bdSJames Wright PetscCheck(ndims == dims[1], comm, PETSC_ERR_FILE_UNEXPECTED, 38*7e3656bdSJames Wright "Line %" PetscInt_FMT " of %s does not contain correct number of columns (%d instead of %d)", i, path, ndims, dims[1]); 39*7e3656bdSJames Wright 40*7e3656bdSJames Wright node_locs[i] = (PetscReal)atof(array[0]); 41*7e3656bdSJames Wright PetscCall(PetscStrToArrayDestroy(ndims, array)); 42*7e3656bdSJames Wright } 43*7e3656bdSJames Wright PetscCall(PetscFClose(comm, fp)); 44*7e3656bdSJames Wright *pynodes = node_locs; 45*7e3656bdSJames Wright PetscFunctionReturn(PETSC_SUCCESS); 46*7e3656bdSJames Wright } 47*7e3656bdSJames Wright 48*7e3656bdSJames Wright /* @brief Modify the domain and mesh for blasius 49*7e3656bdSJames Wright * 50*7e3656bdSJames Wright * Modifies mesh such that `N` elements are within `refine_height` with a geometric growth ratio of `growth`. Excess elements are then distributed 51*7e3656bdSJames Wright * linearly in logspace to the top surface. 52*7e3656bdSJames Wright * 53*7e3656bdSJames Wright * The top surface is also angled downwards, so that it may be used as an outflow. 54*7e3656bdSJames Wright * It's angle is controlled by `top_angle` (in units of degrees). 55*7e3656bdSJames Wright * 56*7e3656bdSJames Wright * If `node_locs` is not NULL, then the nodes will be placed at `node_locs` locations. 57*7e3656bdSJames Wright * If it is NULL, then the modified coordinate values will be set in the array, along with `num_node_locs`. 58*7e3656bdSJames Wright */ 59*7e3656bdSJames Wright static PetscErrorCode HoneeMeshTransform_PlateMesh(MPI_Comm comm, DM dm, PetscReal growth, PetscInt N, PetscReal refine_height, PetscReal top_angle, 60*7e3656bdSJames Wright PetscReal *node_locs[], PetscInt *num_node_locs) { 61*7e3656bdSJames Wright PetscInt narr, ncoords, dim; 62*7e3656bdSJames Wright PetscReal domain_min[3], domain_max[3], domain_size[3]; 63*7e3656bdSJames Wright PetscScalar *arr_coords; 64*7e3656bdSJames Wright Vec vec_coords; 65*7e3656bdSJames Wright 66*7e3656bdSJames Wright PetscFunctionBeginUser; 67*7e3656bdSJames Wright PetscCall(DMGetDimension(dm, &dim)); 68*7e3656bdSJames Wright PetscReal angle_coeff = tan(top_angle * (M_PI / 180)); 69*7e3656bdSJames Wright // Get domain boundary information 70*7e3656bdSJames Wright PetscCall(DMGetBoundingBox(dm, domain_min, domain_max)); 71*7e3656bdSJames Wright for (PetscInt i = 0; i < 3; i++) domain_size[i] = domain_max[i] - domain_min[i]; 72*7e3656bdSJames Wright 73*7e3656bdSJames Wright // Get coords array from DM 74*7e3656bdSJames Wright PetscCall(DMGetCoordinatesLocal(dm, &vec_coords)); 75*7e3656bdSJames Wright PetscCall(VecGetLocalSize(vec_coords, &narr)); 76*7e3656bdSJames Wright PetscCall(VecGetArray(vec_coords, &arr_coords)); 77*7e3656bdSJames Wright 78*7e3656bdSJames Wright PetscScalar(*coords)[dim] = (PetscScalar(*)[dim])arr_coords; 79*7e3656bdSJames Wright ncoords = narr / dim; 80*7e3656bdSJames Wright 81*7e3656bdSJames Wright // Get mesh information 82*7e3656bdSJames Wright PetscInt nmax = 3, faces[3]; 83*7e3656bdSJames Wright PetscCall(PetscOptionsGetIntArray(NULL, NULL, "-dm_plex_box_faces", faces, &nmax, NULL)); 84*7e3656bdSJames Wright // Get element size of the box mesh, for indexing each node 85*7e3656bdSJames Wright const PetscReal dybox = domain_size[1] / faces[1]; 86*7e3656bdSJames Wright 87*7e3656bdSJames Wright if (!*node_locs) { 88*7e3656bdSJames Wright // Calculate the first element height 89*7e3656bdSJames Wright PetscReal dy1 = refine_height * (growth - 1) / (pow(growth, N) - 1); 90*7e3656bdSJames Wright 91*7e3656bdSJames Wright // Calculate log of sizing outside BL 92*7e3656bdSJames Wright PetscReal logdy = (log(domain_max[1]) - log(refine_height)) / (faces[1] - N); 93*7e3656bdSJames Wright 94*7e3656bdSJames Wright *num_node_locs = faces[1] + 1; 95*7e3656bdSJames Wright PetscReal *temp_node_locs; 96*7e3656bdSJames Wright PetscCall(PetscMalloc1(*num_node_locs, &temp_node_locs)); 97*7e3656bdSJames Wright 98*7e3656bdSJames Wright for (PetscInt i = 0; i < ncoords; i++) { 99*7e3656bdSJames Wright PetscInt y_box_index = round(coords[i][1] / dybox); 100*7e3656bdSJames Wright if (y_box_index <= N) { 101*7e3656bdSJames Wright coords[i][1] = 102*7e3656bdSJames Wright (1 - (coords[i][0] - domain_min[0]) * angle_coeff / domain_max[1]) * dy1 * (pow(growth, coords[i][1] / dybox) - 1) / (growth - 1); 103*7e3656bdSJames Wright } else { 104*7e3656bdSJames Wright PetscInt j = y_box_index - N; 105*7e3656bdSJames Wright coords[i][1] = (1 - (coords[i][0] - domain_min[0]) * angle_coeff / domain_max[1]) * exp(log(refine_height) + logdy * j); 106*7e3656bdSJames Wright } 107*7e3656bdSJames Wright if (coords[i][0] == domain_min[0] && coords[i][2] == domain_min[2]) temp_node_locs[y_box_index] = coords[i][1]; 108*7e3656bdSJames Wright } 109*7e3656bdSJames Wright 110*7e3656bdSJames Wright *node_locs = temp_node_locs; 111*7e3656bdSJames Wright } else { 112*7e3656bdSJames Wright PetscCheck(*num_node_locs >= faces[1] + 1, comm, PETSC_ERR_FILE_UNEXPECTED, 113*7e3656bdSJames Wright "The y_node_locs_path has too few locations; There are %" PetscInt_FMT " + 1 nodes, but only %" PetscInt_FMT " locations given", 114*7e3656bdSJames Wright faces[1] + 1, *num_node_locs); 115*7e3656bdSJames Wright if (*num_node_locs > faces[1] + 1) { 116*7e3656bdSJames Wright PetscCall(PetscPrintf(comm, 117*7e3656bdSJames Wright "WARNING: y_node_locs_path has more locations (%" PetscInt_FMT ") " 118*7e3656bdSJames Wright "than the mesh has nodes (%" PetscInt_FMT "). This maybe unintended.\n", 119*7e3656bdSJames Wright *num_node_locs, faces[1] + 1)); 120*7e3656bdSJames Wright } 121*7e3656bdSJames Wright PetscScalar max_y = (*node_locs)[faces[1]]; 122*7e3656bdSJames Wright 123*7e3656bdSJames Wright for (PetscInt i = 0; i < ncoords; i++) { 124*7e3656bdSJames Wright // Determine which y-node we're at 125*7e3656bdSJames Wright PetscInt y_box_index = round(coords[i][1] / dybox); 126*7e3656bdSJames Wright coords[i][1] = (1 - (coords[i][0] - domain_min[0]) * angle_coeff / max_y) * (*node_locs)[y_box_index]; 127*7e3656bdSJames Wright } 128*7e3656bdSJames Wright } 129*7e3656bdSJames Wright 130*7e3656bdSJames Wright PetscCall(VecRestoreArray(vec_coords, &arr_coords)); 131*7e3656bdSJames Wright PetscCall(DMSetCoordinatesLocal(dm, vec_coords)); 132*7e3656bdSJames Wright PetscFunctionReturn(PETSC_SUCCESS); 133*7e3656bdSJames Wright } 134*7e3656bdSJames Wright 135*7e3656bdSJames Wright /** 136*7e3656bdSJames Wright @brief Transform the mesh based on pass options 137*7e3656bdSJames Wright 138*7e3656bdSJames Wright Use the option `-mesh_transform` to determine the type of mesh transformation to apply 139*7e3656bdSJames Wright 140*7e3656bdSJames Wright @param dm `DM` of mesh to be transformed 141*7e3656bdSJames Wright **/ 142*7e3656bdSJames Wright PetscErrorCode HoneeMeshTransformFromOptions(DM dm) { 143*7e3656bdSJames Wright MPI_Comm comm = PetscObjectComm((PetscObject)dm); 144*7e3656bdSJames Wright 145*7e3656bdSJames Wright PetscFunctionBeginUser; 146*7e3656bdSJames Wright PetscOptionsBegin(comm, NULL, "Options for HONEE Mesh Transformation", NULL); 147*7e3656bdSJames Wright MeshTransform mesh_transform_type = MESH_TRANSFORM_NONE; 148*7e3656bdSJames Wright PetscCall(PetscOptionsDeprecated("-mesh_transform", "-meshtransform_type", "HONEE 0.0", NULL)); 149*7e3656bdSJames Wright PetscCall(PetscOptionsEnum("-meshtransform_type", "Mesh transform to perform", NULL, MeshTransforms, (PetscEnum)mesh_transform_type, 150*7e3656bdSJames Wright (PetscEnum *)&mesh_transform_type, NULL)); 151*7e3656bdSJames Wright 152*7e3656bdSJames Wright switch (mesh_transform_type) { 153*7e3656bdSJames Wright case MESH_TRANSFORM_PLATEMESH: { 154*7e3656bdSJames Wright PetscReal mesh_refine_height = 5.9e-4; // m 155*7e3656bdSJames Wright PetscReal mesh_growth = 1.08; // [-] 156*7e3656bdSJames Wright PetscInt mesh_Ndelta = 45; // [-] 157*7e3656bdSJames Wright PetscReal mesh_top_angle = 5; // degrees 158*7e3656bdSJames Wright char mesh_ynodes_path[PETSC_MAX_PATH_LEN] = ""; 159*7e3656bdSJames Wright 160*7e3656bdSJames Wright PetscCall(PetscOptionsDeprecated("-platemesh_Ndelta", "-meshtransform_platemesh_Ndelta", "HONEE 0.0", NULL)); 161*7e3656bdSJames Wright PetscCall(PetscOptionsDeprecated("-platemesh_refine_height", "-meshtransform_platemesh_refine_height", "HONEE 0.0", NULL)); 162*7e3656bdSJames Wright PetscCall(PetscOptionsDeprecated("-platemesh_growth", "-meshtransform_platemesh_growth", "HONEE 0.0", NULL)); 163*7e3656bdSJames Wright PetscCall(PetscOptionsDeprecated("-platemesh_top_angle", "-meshtransform_platemesh_top_angle", "HONEE 0.0", NULL)); 164*7e3656bdSJames Wright PetscCall(PetscOptionsDeprecated("-platemesh_y_node_locs_path", "-meshtransform_platemesh_y_node_locs_path", "HONEE 0.0", NULL)); 165*7e3656bdSJames Wright 166*7e3656bdSJames Wright PetscCall(PetscOptionsBoundedInt("-meshtransform_platemesh_Ndelta", "Velocity at boundary layer edge", NULL, mesh_Ndelta, &mesh_Ndelta, NULL, 167*7e3656bdSJames Wright 1)); 168*7e3656bdSJames Wright PetscCall(PetscOptionsScalar("-meshtransform_platemesh_refine_height", "Height of boundary layer mesh refinement", NULL, mesh_refine_height, 169*7e3656bdSJames Wright &mesh_refine_height, NULL)); 170*7e3656bdSJames Wright PetscCall(PetscOptionsScalar("-meshtransform_platemesh_growth", "Geometric growth rate of boundary layer mesh", NULL, mesh_growth, &mesh_growth, 171*7e3656bdSJames Wright NULL)); 172*7e3656bdSJames Wright PetscCall(PetscOptionsScalar("-meshtransform_platemesh_top_angle", "Geometric top_angle rate of boundary layer mesh", NULL, mesh_top_angle, 173*7e3656bdSJames Wright &mesh_top_angle, NULL)); 174*7e3656bdSJames Wright PetscCall(PetscOptionsString("-meshtransform_platemesh_y_node_locs_path", 175*7e3656bdSJames Wright "Path to file with y node locations. " 176*7e3656bdSJames Wright "If empty, will use the algorithmic mesh warping.", 177*7e3656bdSJames Wright NULL, mesh_ynodes_path, mesh_ynodes_path, sizeof(mesh_ynodes_path), NULL)); 178*7e3656bdSJames Wright PetscReal *mesh_ynodes = NULL; 179*7e3656bdSJames Wright PetscInt mesh_nynodes = 0; 180*7e3656bdSJames Wright if (strcmp(mesh_ynodes_path, "")) PetscCall(GetYNodeLocs(comm, mesh_ynodes_path, &mesh_ynodes, &mesh_nynodes)); 181*7e3656bdSJames Wright PetscCall(HoneeMeshTransform_PlateMesh(comm, dm, mesh_growth, mesh_Ndelta, mesh_refine_height, mesh_top_angle, &mesh_ynodes, &mesh_nynodes)); 182*7e3656bdSJames Wright PetscCall(PetscFree(mesh_ynodes)); 183*7e3656bdSJames Wright } break; 184*7e3656bdSJames Wright case MESH_TRANSFORM_NONE: 185*7e3656bdSJames Wright break; 186*7e3656bdSJames Wright } 187*7e3656bdSJames Wright PetscOptionsEnd(); 188*7e3656bdSJames Wright 189*7e3656bdSJames Wright { // Scale coordinates based on DM length scale 190*7e3656bdSJames Wright // We want to make sure to do the scaling *after* the mesh transformation, so units in the transform are physical 191*7e3656bdSJames Wright Vec coords; 192*7e3656bdSJames Wright PetscReal meter; 193*7e3656bdSJames Wright PetscCall(DMPlexGetScale(dm, PETSC_UNIT_LENGTH, &meter)); 194*7e3656bdSJames Wright PetscCall(DMGetCoordinatesLocal(dm, &coords)); 195*7e3656bdSJames Wright PetscCall(VecScale(coords, meter)); 196*7e3656bdSJames Wright PetscCall(DMSetCoordinatesLocal(dm, coords)); 197*7e3656bdSJames Wright } 198*7e3656bdSJames Wright 199*7e3656bdSJames Wright PetscFunctionReturn(PETSC_SUCCESS); 200*7e3656bdSJames Wright } 201