#include "SELF_GPU_Macros.h" // Device-side AMR solution transfer (Stage 6a). // // Applies a prebuilt transfer plan on the device, replacing the host round trip that the // adaptive loop previously performed every epoch (solution UpdateHost -> host // ApplyTransferPlanRange -> solution UpdateDevice). The mathematics mirror the portable // reference implementation exactly: SELF_TransferPlan_2D.f90 (ApplyTransferPlanRange) driving // SELF_SolutionTransfer_2D.f90 (ProlongToChildren / RestrictFromChildren). // // Each new element is classified by the plan as // // COPY - the element is unchanged; take the old element's nodal values verbatim. // PROLONG - the element descends from a surviving coarser ancestor; interpolate down. // RESTRICT - the element replaces a family of four children; L2-project them up. // // and may then require `depth` further prolongation steps down a recorded quadrant `path`. // Both element-local operators are tensor products of the 1-D mortar matrices, applied as an // xi pass followed by an eta pass: // // prolong: tmp(i,j) = sum_ii mortarR(ii,i,kx) * parent(ii,j) // child(i,j)= sum_jj mortarR(jj,j,ky) * tmp(i,jj) // restrict: tmp(i,jj) = sum_ii mortarP(ii,i,kx) * child(ii,jj) // parent += sum_jj mortarP(jj,j,ky) * tmp(i,jj) (accumulated over children) // // mortarP carries the sub-edge Jacobian, which is what makes restriction conservative. // // Deliberate divergence from the host reference: the host descent calls ProlongToChildren, // which forms all four children and discards three (SELF_TransferPlan_2D.f90:283-286). This // kernel applies only the operator pair of the child actually on the path, doing a quarter of // the work per descent step. The arithmetic per retained value is identical in form but the // operation order across a descent differs, so device and host results agree to round-off // rather than bitwise; conservation of the Jacobian-weighted integral is exact either way and // is what the tests assert. // // Thread mapping: one workgroup per new element, one thread per (i,j) node of that element, // variables handled sequentially. The Np x Np working buffers therefore live in shared memory // instead of per-thread scratch, which a thread-per-element mapping would spill to scratch // memory at realistic degree and variable counts. // Maximum supported (N+1). Sizes the shared working buffers below; the Fortran caller guards // the degree so an unsupported N fails loudly rather than overrunning. Matches the bound used // by the modal indicator in SELF_Refinement.cpp. #define AMR2D_MAXNP 16 // Transfer-kind tags. Must match the SELF_TRANSFER_* parameters in SELF_TransferPlan_2D.f90. #define SELF_TRANSFER_COPY 0 #define SELF_TRANSFER_PROLONG 1 #define SELF_TRANSFER_RESTRICT 2 // Child quadrant (1=SW,2=SE,3=NE,4=NW) -> (x-half, y-half); half h selects mortar sub-edge // k = h. Mirrors transferAxc / transferAyc in SELF_SolutionTransfer_2D.f90, less the Fortran // 1-based offset. __device__ __constant__ int d_transferAxc[4] = {0, 1, 1, 0}; __device__ __constant__ int d_transferAyc[4] = {0, 0, 1, 1}; __global__ void TransferSolution_2D_gpukernel(real *uOld, real *uNew, int *sourceKind, int *sourceElem, int *family, int *depth, int *path, real *mortarR, real *mortarP, int pathStride, int eFirst0, int N, int nvar, int nOld, int nNew){ // One block per new (rank-local) element. int lo = blockIdx.x; // 0-based index into the rank-local new field int gi = eFirst0 + lo; // 0-based index into the plan's global new-leaf arrays int Np = N+1; int tid = threadIdx.x; // blockDim.x == Np*Np, so every thread is active int i = tid % Np; int j = tid / Np; __shared__ real buf[AMR2D_MAXNP*AMR2D_MAXNP]; __shared__ real tmp[AMR2D_MAXNP*AMR2D_MAXNP]; __shared__ real acc[AMR2D_MAXNP*AMR2D_MAXNP]; int kind = sourceKind[gi]; int d = depth[gi]; for(int v=0; v<nvar; v++){ if( kind == SELF_TRANSFER_RESTRICT ){ // L2-project the four children onto their parent, accumulating over children. acc[i + Np*j] = 0.0; __syncthreads(); for(int c=0; c<4; c++){ int src = family[c + 4*gi] - 1; // Fortran 1-based element index int kx = d_transferAxc[c]; int ky = d_transferAyc[c]; buf[i + Np*j] = uOld[SC_2D_INDEX(i,j,src,v,N,nOld)]; __syncthreads(); // xi pass: tmp(i,jj) = sum_ii mortarP(ii,i,kx) * child(ii,jj) real s = 0.0; for(int ii=0; ii<Np; ii++){ s += mortarP[ii + Np*(i + Np*kx)]*buf[ii + Np*j]; } tmp[i + Np*j] = s; __syncthreads(); // eta pass, accumulated over the four children s = 0.0; for(int jj=0; jj<Np; jj++){ s += mortarP[jj + Np*(j + Np*ky)]*tmp[i + Np*jj]; } acc[i + Np*j] += s; __syncthreads(); } buf[i + Np*j] = acc[i + Np*j]; __syncthreads(); } else { // COPY (d == 0) and PROLONG (d > 0) both start from a single surviving old element. int src = sourceElem[gi] - 1; buf[i + Np*j] = uOld[SC_2D_INDEX(i,j,src,v,N,nOld)]; __syncthreads(); } // Descend the recorded path, prolonging one level per step onto the child that is on the // path (rather than onto all four). for(int step=0; step<d; step++){ // pathStride is the ALLOCATED leading dimension of plan%path, i.e. // max(forest%MaxLevel(),1) - not plan%maxDepth, which is the largest depth actually // encountered and is generally smaller. int c = path[step + pathStride*gi] - 1; // quadrant 1..4 -> 0..3 int kx = d_transferAxc[c]; int ky = d_transferAyc[c]; // xi pass: tmp(i,j) = sum_ii mortarR(ii,i,kx) * parent(ii,j) real s = 0.0; for(int ii=0; ii<Np; ii++){ s += mortarR[ii + Np*(i + Np*kx)]*buf[ii + Np*j]; } tmp[i + Np*j] = s; __syncthreads(); // eta pass: child(i,j) = sum_jj mortarR(jj,j,ky) * tmp(i,jj) s = 0.0; for(int jj=0; jj<Np; jj++){ s += mortarR[jj + Np*(j + Np*ky)]*tmp[i + Np*jj]; } buf[i + Np*j] = s; __syncthreads(); } uNew[SC_2D_INDEX(i,j,lo,v,N,nNew)] = buf[i + Np*j]; __syncthreads(); } } extern "C" { void TransferSolution_2D_gpu(real *uOld, real *uNew, int *sourceKind, int *sourceElem, int *family, int *depth, int *path, real *mortarR, real *mortarP, int pathStride, int eFirst0, int N, int nvar, int nOld, int nNew, int nLocal) { if( nLocal <= 0 ){ return; } int Np = N+1; TransferSolution_2D_gpukernel<<<dim3(nLocal,1,1), dim3(Np*Np,1,1), 0, 0>>>( uOld, uNew, sourceKind, sourceElem, family, depth, path, mortarR, mortarP, pathStride, eFirst0, N, nvar, nOld, nNew); } }