@@ -299,4 +299,110 @@ PetscOperator PetscOperators::get(const std::string& name) const {
299299 this ->meshGetArray <BoutReal>(this ->mesh , name + " _weights" ));
300300}
301301
302+ PetscOperators::Parallel PetscOperators::getParallel () const {
303+ // Read maps from the mesh
304+ auto forward = this ->get (" forward" );
305+ auto backward = this ->get (" backward" );
306+
307+ // ---- Construct Grad_par ----
308+ //
309+ // Create a parallel gradient operator by combining the parallel
310+ // length dl = dy * sqrt(g_22) with forward & backward operators
311+ auto * coords = this ->mesh ->getCoordinates ();
312+ Field3D dl = coords->dy * sqrt (coords->g_22 );
313+ dl.splitParallelSlices ();
314+ dl.yup () = 0.0 ;
315+ dl.ydown () = 0.0 ;
316+ dl.applyParallelBoundary (" parallel_neumann_o1" );
317+
318+ auto inv_2dl = 0.5 / dl;
319+ inv_2dl.splitParallelSlices ();
320+ inv_2dl.yup () = 0.0 ;
321+ inv_2dl.ydown () = 0.0 ;
322+ inv_2dl.applyParallelBoundary (" parallel_neumann_o1" );
323+
324+ inv_2dl.yup () *= 0.5 ;
325+ inv_2dl.ydown () *= 0.5 ;
326+
327+ auto inv_2dl_op = this ->diagonal (inv_2dl);
328+
329+ auto Grad_par = inv_2dl_op * (forward - backward);
330+
331+ // ---- Construct Div_par ----
332+ //
333+ // Use the Support Operator Method (SOM) to calculate
334+ // Div_par from Grad_par and cell volumes.
335+
336+ // Cell volume
337+ auto dV = coords->J * coords->dx * coords->dy * coords->dz ;
338+ dV.splitParallelSlices ();
339+ dV.yup () = 0.0 ;
340+ dV.ydown () = 0.0 ;
341+ dV.applyParallelBoundary (" parallel_neumann_o1" );
342+ auto dV_op = this ->diagonal (dV);
343+
344+ Field3D neg_inv_dV = -1 . / dV;
345+ neg_inv_dV.splitParallelSlices ();
346+ neg_inv_dV.yup () = 0.0 ;
347+ neg_inv_dV.ydown () = 0.0 ;
348+ neg_inv_dV.applyParallelBoundary (" parallel_neumann_o1" );
349+ auto neg_inv_dV_op = this ->diagonal (neg_inv_dV);
350+
351+ auto Div_par = neg_inv_dV_op * Grad_par.transpose () * dV_op;
352+
353+ // ---- Construct Div_par_Grad_par ----
354+ //
355+ // Requires gradients between planes, at +1/2 and -1/2, and interpolation
356+ // operators to calculate quantities between cells.
357+
358+ // Identity operator
359+ Field3D one{1.0 };
360+ one.splitParallelSlices ();
361+ one.yup () = 1.0 ;
362+ one.ydown () = 1.0 ;
363+ const auto identity = this ->diagonal (one);
364+
365+ // Interpolate at + 1/2
366+ const auto interp_plus_op = (identity + forward) * 0.5 ;
367+
368+ // dl averaged at +1/2
369+ const Field3D dl_plus = interp_plus_op (dl);
370+ Field3D inv_dl_plus = 1 . / dl_plus;
371+ inv_dl_plus.splitParallelSlices ();
372+ inv_dl_plus.yup () = 0.0 ;
373+ inv_dl_plus.ydown () = 0.0 ;
374+ inv_dl_plus.applyParallelBoundary (" parallel_neumann_o1" );
375+ const auto inv_dl_plus_op = this ->diagonal (inv_dl_plus);
376+
377+ // Gradient at + 1/2
378+ const auto Grad_plus = inv_dl_plus_op * (forward - identity);
379+
380+ // Divergence at -1/2
381+ const auto Div_minus = neg_inv_dV_op * Grad_plus.transpose () * dV_op;
382+
383+ // Interpolate at - 1/2
384+ auto interp_minus_op = (identity + backward) * 0.5 ;
385+
386+ // dl averaged at -1/2
387+ const Field3D dl_minus = interp_minus_op (dl);
388+ Field3D inv_dl_minus = 1 . / dl_minus;
389+ inv_dl_minus.splitParallelSlices ();
390+ inv_dl_minus.yup () = 0.0 ;
391+ inv_dl_minus.ydown () = 0.0 ;
392+ inv_dl_minus.applyParallelBoundary (" parallel_neumann_o1" );
393+ const auto inv_dl_minus_op = this ->diagonal (inv_dl_minus);
394+
395+ // Gradient at - 1/2
396+ auto Grad_minus = inv_dl_minus_op * (identity - backward);
397+
398+ // Divergence at +1/2
399+ const auto Div_plus = neg_inv_dV_op * Grad_minus.transpose () * dV_op;
400+
401+ // Div(Grad_par()) operator
402+ auto Div_par_Grad_par = ((Div_minus * Grad_plus) + (Div_plus * Grad_minus)) * 0.5 ;
403+
404+ return Parallel{std::move (Grad_par), std::move (Div_par), std::move (Div_par_Grad_par),
405+ std::move (dV)};
406+ }
407+
302408#endif // BOUT_HAS_PETSC
0 commit comments