PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Enumerations | Functions
Boundaries.h File Reference
#include <petscpf.h>
#include <petscdmswarm.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <petsctime.h>
#include <petscsys.h>
#include <petscdmcomposite.h>
#include <petscsystypes.h>
#include "variables.h"
#include "field_catalog.h"
#include "ParticleSwarm.h"
#include "walkingsearch.h"
#include "grid.h"
#include "logging.h"
#include "io.h"
#include "interpolation.h"
#include "ParticleMotion.h"
#include "BC_Handlers.h"
#include "wallfunction.h"
Include dependency graph for Boundaries.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Enumerations

enum  MomentumRowType { MOM_ROW_PHYSICAL = 0 , MOM_ROW_FIXED_CONDITIONED , MOM_ROW_FIXED_HOMOGENEOUS , MOM_ROW_PERIODIC_DUPLICATE }
 Classification of one staggered momentum row (location + component). More...
 

Functions

PetscErrorCode BoundarySystem_Validate (UserCtx *user)
 (Public) Validates the consistency and compatibility of the parsed boundary condition system.
 
PetscErrorCode BoundaryCondition_Create (BCHandlerType handler_type, BoundaryCondition **new_bc_ptr)
 (Private) Creates and configures a specific BoundaryCondition handler object.
 
PetscErrorCode BoundarySystem_Initialize (UserCtx *user, const char *bcs_filename)
 Initializes the entire boundary system.
 
PetscErrorCode PropagateBoundaryConfigToCoarserLevels (SimCtx *simCtx)
 Propagates boundary condition configuration from finest to all coarser multigrid levels.
 
PetscErrorCode BoundarySystem_ExecuteStep (UserCtx *user)
 Executes one full boundary condition update cycle for a time step.
 
PetscErrorCode BoundarySystem_RefreshUbcs (UserCtx *user)
 (Private) A lightweight execution engine that calls the UpdateUbcs() method on all relevant handlers.
 
PetscErrorCode BoundarySystem_Destroy (UserCtx *user)
 Cleans up and destroys all boundary system resources.
 
PetscErrorCode CanRankServiceInletFace (UserCtx *user, const DMDALocalInfo *info, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, PetscBool *can_service_inlet_out)
 Determines if the current MPI rank owns any part of the globally defined inlet face, making it responsible for placing particles on that portion of the surface.
 
PetscErrorCode CanRankServiceFace (const DMDALocalInfo *info, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, BCFace face_id, PetscBool *can_service_out)
 Determines if the current MPI rank owns any part of a specified global face.
 
PetscErrorCode GetDeterministicFaceGridLocation (UserCtx *user, const DMDALocalInfo *info, PetscInt xs_gnode_rank, PetscInt ys_gnode_rank, PetscInt zs_gnode_rank, PetscInt IM_cells_global, PetscInt JM_cells_global, PetscInt KM_cells_global, PetscInt64 particle_global_id, PetscInt *ci_metric_lnode_out, PetscInt *cj_metric_lnode_out, PetscInt *ck_metric_lnode_out, PetscReal *xi_metric_logic_out, PetscReal *eta_metric_logic_out, PetscReal *zta_metric_logic_out, PetscBool *placement_successful_out)
 Places particles in a deterministic grid/raster pattern on a specified domain face.
 
PetscErrorCode GetRandomCellAndLogicalCoordsOnInletFace (UserCtx *user, const DMDALocalInfo *info, PetscInt xs_gnode_rank, PetscInt ys_gnode_rank, PetscInt zs_gnode_rank, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, PetscRandom *rand_logic_i_ptr, PetscRandom *rand_logic_j_ptr, PetscRandom *rand_logic_k_ptr, PetscInt *ci_metric_lnode_out, PetscInt *cj_metric_lnode_out, PetscInt *ck_metric_lnode_out, PetscReal *xi_metric_logic_out, PetscReal *eta_metric_logic_out, PetscReal *zta_metric_logic_out)
 Assuming the current rank services the inlet face, this function selects a random cell (owned by this rank on that face) and random logical coordinates within that cell, suitable for placing a particle on the inlet surface.
 
MomentumRowType ClassifyMomentumRow (UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscInt component, PetscInt *ri, PetscInt *rj, PetscInt *rk)
 Single source of truth for "which staggered momentum rows are unknowns".
 
PetscBool MomentumRowIsSolidMasked (const PetscReal ***nvert, PetscInt i, PetscInt j, PetscInt k, PetscInt component)
 Reports whether a momentum row is masked out by the solid-cell field.
 
PetscErrorCode EnforceRHSBoundaryConditions (UserCtx *user)
 Zeroes every momentum RHS row that does not carry an independent unknown.
 
PetscErrorCode SynchronizePeriodicCellFields (UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
 Synchronizes periodic endpoint cells for a list of cell-centered fields.
 
PetscErrorCode SynchronizePeriodicFaceFields (UserCtx *user, char face_direction, PetscInt num_fields, const FieldId field_ids[])
 Synchronizes persistent fields belonging to one face family.
 
PetscErrorCode SynchronizePeriodicStaggeredFields (UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
 Synchronizes persistent component-staggered vector fields.
 
PetscErrorCode PreparePeriodicQuickStencilFields (UserCtx *user, Vec local_vector_field, Vec local_scalar_field)
 Repairs the outer adjacent periodic ghosts used by QUICK cell stencils.
 
PetscErrorCode SynchronizePeriodicLocalStaggeredField (UserCtx *user, Vec local_field)
 Synchronizes one local-only component-staggered periodic work field.
 
PetscErrorCode ApplyMetricsPeriodicBCs (UserCtx *user)
 (Orchestrator) Updates all metric-related fields in the local ghost cell regions for periodic boundaries.
 
PetscErrorCode ApplyPeriodicBCs (UserCtx *user)
 Applies periodic boundary conditions by copying data across domain boundaries for all relevant fields.
 
PetscErrorCode UpdateDummyCells (UserCtx *user, FieldId field_id)
 Fills the dummy cells on the non-periodic faces of one cell-centred field.
 
PetscErrorCode UpdateCornerNodes (UserCtx *user, FieldId field_id)
 Fills the edge and corner dummy cells of one cell-centred field by averaging.
 
PetscErrorCode ApplyWallFunction (UserCtx *user)
 Applies wall function modeling to near-wall velocities for all wall-type boundaries.
 
PetscErrorCode LogWallModelDiagnostics (UserCtx *user)
 Appends one row of near-wall statistics to <run.analysis.metrics>/wall_model.csv.
 
PetscErrorCode FinalizePostProjectionCellFields (UserCtx *user)
 Finalizes cell-centered fields after the projection step.
 
PetscErrorCode ApplyBoundaryConditions (UserCtx *user)
 Main boundary-condition orchestrator executed during solver timestepping.
 

Enumeration Type Documentation

◆ MomentumRowType

Classification of one staggered momentum row (location + component).

See also
ClassifyMomentumRow() for the meaning of each member and for the single-source-of-truth contract these values participate in.
Enumerator
MOM_ROW_PHYSICAL 

Independent unknown governed by the momentum equation.

MOM_ROW_FIXED_CONDITIONED 

Strong Dirichlet row; the value comes from ApplyBoundaryConditions().

MOM_ROW_FIXED_HOMOGENEOUS 

Dummy/tangential row carrying no unknown at all.

MOM_ROW_PERIODIC_DUPLICATE 

Duplicate of a wrapped representative row (see ri, rj, rk).

Definition at line 252 of file Boundaries.h.

252 {
253 MOM_ROW_PHYSICAL = 0, /**< Independent unknown governed by the momentum equation. */
254 MOM_ROW_FIXED_CONDITIONED, /**< Strong Dirichlet row; the value comes from ApplyBoundaryConditions(). */
255 MOM_ROW_FIXED_HOMOGENEOUS, /**< Dummy/tangential row carrying no unknown at all. */
256 MOM_ROW_PERIODIC_DUPLICATE /**< Duplicate of a wrapped representative row (see @p ri, @p rj, @p rk). */
MomentumRowType
Classification of one staggered momentum row (location + component).
Definition Boundaries.h:252
@ MOM_ROW_FIXED_HOMOGENEOUS
Dummy/tangential row carrying no unknown at all.
Definition Boundaries.h:255
@ MOM_ROW_PHYSICAL
Independent unknown governed by the momentum equation.
Definition Boundaries.h:253
@ MOM_ROW_PERIODIC_DUPLICATE
Duplicate of a wrapped representative row (see ri, rj, rk).
Definition Boundaries.h:256
@ MOM_ROW_FIXED_CONDITIONED
Strong Dirichlet row; the value comes from ApplyBoundaryConditions().
Definition Boundaries.h:254

Function Documentation

◆ BoundarySystem_Validate()

PetscErrorCode BoundarySystem_Validate ( UserCtx *  user)

(Public) Validates the consistency and compatibility of the parsed boundary condition system.

This function is the main entry point for all boundary condition validation. It should be called from the main setup sequence AFTER the configuration file has been parsed by ParseAllBoundaryConditions but BEFORE any BoundaryCondition handler objects are created.

It acts as a dispatcher, calling specialized private sub-validators for different complex BC setups (like driven flow) to ensure the combination of mathematical_type and handler_type across all six faces is physically and numerically valid. This provides a "fail-fast" mechanism to prevent users from running improperly configured simulations.

Parameters
userThe UserCtx for a single block, containing the populated boundary_faces configuration.
Returns
PetscErrorCode 0 on success, non-zero PETSc error code on failure.

(Public) Validates the consistency and compatibility of the parsed boundary condition system.

Local to this translation unit.

Definition at line 815 of file Boundaries.c.

816{
817 PetscErrorCode ierr;
818 const BCFace neg_faces[3] = {BC_FACE_NEG_X, BC_FACE_NEG_Y, BC_FACE_NEG_Z};
819 const BCFace pos_faces[3] = {BC_FACE_POS_X, BC_FACE_POS_Y, BC_FACE_POS_Z};
820 const char axis_names[3] = {'X', 'Y', 'Z'};
821 DMBoundaryType bx, by, bz;
822 PetscBool dm_periodic[3];
823 PetscFunctionBeginUser;
824
825 LOG_ALLOW(GLOBAL, LOG_INFO, "Validating parsed boundary condition configuration...\n");
826 ierr = DMDAGetInfo(user->da, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
827 &bx, &by, &bz, NULL); CHKERRQ(ierr);
828 dm_periodic[0] = (PetscBool)(bx == DM_BOUNDARY_PERIODIC);
829 dm_periodic[1] = (PetscBool)(by == DM_BOUNDARY_PERIODIC);
830 dm_periodic[2] = (PetscBool)(bz == DM_BOUNDARY_PERIODIC);
831
832 // --- Rule Set 1: Geometric periodic faces must be paired and match the DM topology. ---
833 for (PetscInt axis = 0; axis < 3; axis++) {
834 const PetscBool neg_periodic =
835 user->boundary_faces[neg_faces[axis]].mathematical_type == PERIODIC;
836 const PetscBool pos_periodic =
837 user->boundary_faces[pos_faces[axis]].mathematical_type == PERIODIC;
838
839 PetscCheck(neg_periodic == pos_periodic, PETSC_COMM_WORLD, PETSC_ERR_USER_INPUT,
840 "Configuration Error: Periodic boundaries in the %c direction must be paired; "
841 "%s is %s while %s is %s.",
842 axis_names[axis],
843 BCFaceToString(neg_faces[axis]), neg_periodic ? "PERIODIC" : "not periodic",
844 BCFaceToString(pos_faces[axis]), pos_periodic ? "PERIODIC" : "not periodic");
845 PetscCheck(dm_periodic[axis] == neg_periodic, PETSC_COMM_WORLD, PETSC_ERR_USER_INPUT,
846 "Configuration Error: The %c-direction DM periodic flag (%d) does not match "
847 "the paired boundary configuration (%s).",
848 axis_names[axis], (int)dm_periodic[axis], neg_periodic ? "PERIODIC" : "not periodic");
849 }
850
851 // --- Rule Set 2: Driven Flow Handler Consistency ---
852 // This specialized validator will check all rules related to driven flow handlers.
853 ierr = Validate_DrivenFlowConfiguration(user); CHKERRQ(ierr);
854
855 // --- Rule Set 3: (Future Extension) Overset Interface Consistency ---
856 // ierr = Validate_OversetConfiguration(user); CHKERRQ(ierr);
857
858 LOG_ALLOW(GLOBAL, LOG_INFO, "Boundary configuration is valid.\n");
859
860 PetscFunctionReturn(0);
861}
PetscErrorCode Validate_DrivenFlowConfiguration(UserCtx *user)
(Private) Validates all consistency rules for a driven flow (channel/pipe) setup.
Definition BC_Handlers.c:15
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
const char * BCFaceToString(BCFace face)
Returns the canonical log token for a boundary-face enum value.
Definition logging.c:671
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ PERIODIC
Definition variables.h:318
BoundaryFaceConfig boundary_faces[6]
Definition variables.h:1099
BCType mathematical_type
Definition variables.h:392
BCFace
Identifies the six logical faces of a structured computational block.
Definition variables.h:287
@ BC_FACE_NEG_X
Definition variables.h:288
@ BC_FACE_POS_Z
Definition variables.h:290
@ BC_FACE_POS_Y
Definition variables.h:289
@ BC_FACE_NEG_Z
Definition variables.h:290
@ BC_FACE_POS_X
Definition variables.h:288
@ BC_FACE_NEG_Y
Definition variables.h:289
Here is the call graph for this function:
Here is the caller graph for this function:

◆ BoundaryCondition_Create()

PetscErrorCode BoundaryCondition_Create ( BCHandlerType  handler_type,
BoundaryCondition **  new_bc_ptr 
)

(Private) Creates and configures a specific BoundaryCondition handler object.

This function acts as a factory. Based on the requested handler_type, it allocates a BoundaryCondition object and populates it with the correct set of function pointers corresponding to that specific behavior.

Parameters
handler_typeThe specific handler to create (e.g., BC_HANDLER_WALL_NOSLIP).
[out]new_bc_ptrA pointer to where the newly created BoundaryCondition object's address will be stored.
Returns
PetscErrorCode 0 on success.

(Private) Creates and configures a specific BoundaryCondition handler object.

Local to this translation unit.

Definition at line 729 of file Boundaries.c.

730{
731 PetscErrorCode ierr;
732 PetscFunctionBeginUser;
733
734 const char* handler_name = BCHandlerTypeToString(handler_type);
735 LOG_ALLOW(LOCAL, LOG_DEBUG, "Factory called for handler type %s. \n", handler_name);
736
737 ierr = PetscMalloc1(1, new_bc_ptr); CHKERRQ(ierr);
738 BoundaryCondition *bc = *new_bc_ptr;
739
740 bc->type = handler_type;
741 bc->priority = -1; // Default priority; can be overridden in specific handlers
742 bc->data = NULL;
743 bc->Initialize = NULL;
744 bc->PreStep = NULL;
745 bc->Apply = NULL;
746 bc->PostStep = NULL;
747 bc->UpdateUbcs = NULL;
748 bc->Destroy = NULL;
749
750 LOG_ALLOW(LOCAL, LOG_DEBUG, "Allocated generic handler object at address %p.\n", (void*)bc);
751
752 switch (handler_type) {
753
755 LOG_ALLOW(LOCAL, LOG_DEBUG, "Dispatching to Create_OutletConservation().\n");
756 ierr = Create_OutletConservation(bc); CHKERRQ(ierr);
757 break;
758
760 LOG_ALLOW(LOCAL, LOG_DEBUG, "Dispatching to Create_WallNoSlip().\n");
761 ierr = Create_WallNoSlip(bc); CHKERRQ(ierr);
762 break;
763
765 LOG_ALLOW(LOCAL, LOG_DEBUG, "Dispatching to Create_InletConstantVelocity().\n");
766 ierr = Create_InletConstantVelocity(bc); CHKERRQ(ierr);
767 break;
768
770 LOG_ALLOW(LOCAL,LOG_DEBUG,"Dispatching to Create_PeriodicGeometric().\n");
771 ierr = Create_PeriodicGeometric(bc);
772 break;
773
775 LOG_ALLOW(LOCAL,LOG_DEBUG,"Dispatching to Create_PeriodicDrivenConstant().\n");
777 break;
778
780 LOG_ALLOW(LOCAL,LOG_DEBUG,"Dispatching to Create_PeriodicDrivenInitial().\n");
782 break;
783
785 LOG_ALLOW(LOCAL, LOG_DEBUG, "Dispatching to Create_InletParabolicProfile().\n");
786 ierr = Create_InletParabolicProfile(bc); CHKERRQ(ierr);
787 break;
788
790 LOG_ALLOW(LOCAL, LOG_DEBUG, "Dispatching to Create_InletProfileFromFile().\n");
791 ierr = Create_InletProfileFromFile(bc); CHKERRQ(ierr);
792 break;
793 //Add cases for other handlers here in future phases
794
795 default:
796 LOG_ALLOW(GLOBAL, LOG_ERROR, "Handler type (%s) is not recognized or implemented in the factory.\n", handler_name);
797 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_UNKNOWN_TYPE, "Boundary handler type %d (%s) not recognized in factory.\n", handler_type, handler_name);
798 }
799
800 if(bc->priority < 0) {
801 LOG_ALLOW(GLOBAL, LOG_ERROR, "Handler type %d (%s) did not set a valid priority during creation.\n", handler_type, handler_name);
802 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_UNKNOWN_TYPE, "Boundary handler type %d (%s) did not set a valid priority during creation.\n", handler_type, handler_name);
803 }
804
805 LOG_ALLOW(LOCAL, LOG_DEBUG, "Successfully created and configured handler for %s.\n", handler_name);
806 PetscFunctionReturn(0);
807}
PetscErrorCode Create_InletConstantVelocity(BoundaryCondition *bc)
Configures a BoundaryCondition object to behave as a constant velocity inlet.
PetscErrorCode Create_InletProfileFromFile(BoundaryCondition *bc)
Configures a BoundaryCondition object for a file-prescribed inlet profile.
PetscErrorCode Create_PeriodicGeometric(BoundaryCondition *bc)
Configures a BoundaryCondition object for geometric periodic coupling.
PetscErrorCode Create_InletParabolicProfile(BoundaryCondition *bc)
Configures a BoundaryCondition object for a parabolic inlet profile.
PetscErrorCode Create_PeriodicDrivenInitial(BoundaryCondition *bc)
Configures a BoundaryCondition object for initial-flux periodic driving.
PetscErrorCode Create_PeriodicDrivenConstant(BoundaryCondition *bc)
Configures a BoundaryCondition object for periodic driven-flow forcing.
PetscErrorCode Create_WallNoSlip(BoundaryCondition *bc)
Configures a BoundaryCondition object to behave as a no-slip, stationary wall.
PetscErrorCode Create_OutletConservation(BoundaryCondition *bc)
Configures a BoundaryCondition object for conservative outlet treatment.
const char * BCHandlerTypeToString(BCHandlerType handler_type)
Converts a BCHandlerType enum to its string representation.
Definition logging.c:889
#define LOCAL
Logging scope definitions for controlling message output.
Definition logging.h:45
@ LOG_ERROR
Critical errors that may halt the program.
Definition logging.h:29
@ LOG_DEBUG
Detailed debugging information.
Definition logging.h:32
The "virtual table" struct for a boundary condition handler object.
Definition variables.h:377
PetscErrorCode(* PostStep)(BoundaryCondition *self, BCContext *ctx, PetscReal *local_inflow, PetscReal *local_outflow)
Definition variables.h:384
PetscErrorCode(* PreStep)(BoundaryCondition *self, BCContext *ctx, PetscReal *local_inflow, PetscReal *local_outflow)
Definition variables.h:382
BCHandlerType type
Definition variables.h:378
PetscErrorCode(* Destroy)(BoundaryCondition *self)
Definition variables.h:386
PetscErrorCode(* Initialize)(BoundaryCondition *self, BCContext *ctx)
Definition variables.h:381
PetscErrorCode(* UpdateUbcs)(BoundaryCondition *self, BCContext *ctx)
Definition variables.h:385
PetscErrorCode(* Apply)(BoundaryCondition *self, BCContext *ctx)
Definition variables.h:383
BCPriorityType priority
Definition variables.h:379
@ BC_HANDLER_PERIODIC_GEOMETRIC
Definition variables.h:340
@ BC_HANDLER_INLET_PARABOLIC
Definition variables.h:335
@ BC_HANDLER_INLET_CONSTANT_VELOCITY
Definition variables.h:334
@ BC_HANDLER_PERIODIC_DRIVEN_INITIAL_FLUX
Definition variables.h:343
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:342
@ BC_HANDLER_INLET_PROFILE_FROM_FILE
Definition variables.h:336
@ BC_HANDLER_WALL_NOSLIP
Definition variables.h:331
@ BC_HANDLER_OUTLET_CONSERVATION
Definition variables.h:338
Here is the call graph for this function:
Here is the caller graph for this function:

◆ BoundarySystem_Initialize()

PetscErrorCode BoundarySystem_Initialize ( UserCtx *  user,
const char *  bcs_filename 
)

Initializes the entire boundary system.

Parameters
[in,out]userFinest-level block context receiving parsed face configuration.
bcs_filenamePath to the generated boundary-condition definition file.
Returns
PetscErrorCode 0 on success.

Initializes the entire boundary system.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/Boundaries.h.

See also
BoundarySystem_Initialize()

Definition at line 876 of file Boundaries.c.

877{
878 PetscErrorCode ierr;
879 PetscFunctionBeginUser;
880
881 LOG_ALLOW(GLOBAL, LOG_INFO, "Starting creation and initialization of all boundary handlers.\n");
882
883 // =========================================================================
884 // Step 0: Clear any existing boundary handlers (if re-initializing).
885 // This ensures no memory leaks if this function is called multiple times.
886 // =========================================================================
887 for (int i = 0; i < 6; i++) {
888 BoundaryFaceConfig *face_cfg = &user->boundary_faces[i];
889 if (face_cfg->handler) {
890 LOG_ALLOW(LOCAL, LOG_DEBUG, "Destroying existing handler on Face %s before re-initialization.\n", BCFaceToString((BCFace)i));
891 if (face_cfg->handler->Destroy) {
892 ierr = face_cfg->handler->Destroy(face_cfg->handler); CHKERRQ(ierr);
893 }
894 ierr = PetscFree(face_cfg->handler); CHKERRQ(ierr);
895 face_cfg->handler = NULL;
896 }
897 }
898 // =========================================================================
899
900 // Step 0.1: Initiate flux sums to zero
901 user->simCtx->FluxInSum = 0.0;
902 user->simCtx->FluxOutSum = 0.0;
903 user->simCtx->FarFluxInSum = 0.0;
904 user->simCtx->FarFluxOutSum = 0.0;
905 // =========================================================================
906
907 // Step 1: Parse the configuration file to determine user intent.
908 // This function, defined in io.c, populates the configuration enums and parameter
909 // lists within the user->boundary_faces array on all MPI ranks.
910 ierr = ParseAllBoundaryConditions(user, bcs_filename); CHKERRQ(ierr);
911 LOG_ALLOW(GLOBAL, LOG_INFO, "Configuration file '%s' parsed successfully.\n", bcs_filename);
912
913 // Step 1.1: Validate the parsed configuration to ensure there are no Boundary Condition conflicts
914 ierr = BoundarySystem_Validate(user); CHKERRQ(ierr);
915
916 // Step 2: Create and Initialize the handler object for each of the 6 faces.
917 for (int i = 0; i < 6; i++) {
918 BoundaryFaceConfig *face_cfg = &user->boundary_faces[i];
919
920 const char *face_name = BCFaceToString(face_cfg->face_id);
921 const char *type_name = BCTypeToString(face_cfg->mathematical_type);
922 const char *handler_name = BCHandlerTypeToString(face_cfg->handler_type);
923
924 LOG_ALLOW(LOCAL, LOG_DEBUG, "Creating handler for Face %s with Type %s and handler '%s'.\n", face_name, type_name,handler_name);
925
926 // Use the private factory to construct the correct handler object based on the parsed type.
927 // The factory returns a pointer to the new handler object, which we store in the config struct.
928 ierr = BoundaryCondition_Create(face_cfg->handler_type, &face_cfg->handler); CHKERRQ(ierr);
929
930 // Step 3: Call the specific Initialize() method for the newly created handler.
931 // This allows the handler to perform its own setup, like reading parameters from the
932 // face_cfg->params list and setting the initial field values on its face.
933 if (face_cfg->handler && face_cfg->handler->Initialize) {
934 LOG_ALLOW(LOCAL, LOG_DEBUG, "Calling Initialize() method for handler %s(%s) on Face %s.\n",type_name,handler_name,face_name);
935
936 // Prepare the context needed by the Initialize() function.
937 BCContext ctx = {
938 .user = user,
939 .face_id = face_cfg->face_id,
940 .global_inflow_sum = &user->simCtx->FluxInSum, // Global flux sums are not relevant during initialization.
941 .global_outflow_sum = &user->simCtx->FluxOutSum,
942 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
943 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
944 };
945
946 ierr = face_cfg->handler->Initialize(face_cfg->handler, &ctx); CHKERRQ(ierr);
947 } else {
948 LOG_ALLOW(LOCAL, LOG_DEBUG, "Handler %s(%s) for Face %s has no Initialize() method, skipping.\n", type_name,handler_name,face_name);
949 }
950 }
951 // =========================================================================
952 // NO SYNCHRONIZATION NEEDED HERE
953 // =========================================================================
954 // Initialize() only reads parameters and allocates memory.
955 // It does NOT modify field values (Ucat, Ucont, Ubcs).
956 // Field values are set by:
957 // 1. Initial conditions (before this function)
958 // 2. Apply() during timestepping (after this function)
959 // The first call to ApplyBoundaryConditions() will handle synchronization.
960 // =========================================================================
961
962 LOG_ALLOW(GLOBAL, LOG_INFO, "All boundary handlers created and initialized successfully.\n");
963 PetscFunctionReturn(0);
964}
PetscErrorCode BoundarySystem_Validate(UserCtx *user)
Internal helper implementation: BoundarySystem_Validate().
Definition Boundaries.c:815
PetscErrorCode BoundaryCondition_Create(BCHandlerType handler_type, BoundaryCondition **new_bc_ptr)
Internal helper implementation: BoundaryCondition_Create().
Definition Boundaries.c:729
PetscErrorCode ParseAllBoundaryConditions(UserCtx *user, const char *bcs_input_filename)
Parses the boundary conditions file to configure the type, handler, and any associated parameters for...
Definition io.c:845
const char * BCTypeToString(BCType type)
Returns the canonical log token for a boundary mathematical type.
Definition logging.c:869
PetscReal FarFluxInSum
Definition variables.h:959
PetscReal FarFluxOutSum
Definition variables.h:959
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1077
PetscReal FluxOutSum
Definition variables.h:959
BCHandlerType handler_type
Definition variables.h:393
UserCtx * user
Definition variables.h:368
PetscReal FluxInSum
Definition variables.h:959
BoundaryCondition * handler
Definition variables.h:395
Provides execution context for a boundary condition handler.
Definition variables.h:367
Holds the complete configuration for one of the six boundary faces.
Definition variables.h:390
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PropagateBoundaryConfigToCoarserLevels()

PetscErrorCode PropagateBoundaryConfigToCoarserLevels ( SimCtx *  simCtx)

Propagates boundary condition configuration from finest to all coarser multigrid levels.

Coarser levels need BC type information for geometric operations (e.g., periodic corrections) but do NOT need full handler objects since timestepping only occurs at the finest level. This function copies the boundary_faces configuration down the hierarchy.

Parameters
simCtxThe master SimCtx containing the multigrid hierarchy
Returns
PetscErrorCode 0 on success

Propagates boundary condition configuration from finest to all coarser multigrid levels.

Local to this translation unit.

Definition at line 973 of file Boundaries.c.

974{
975 PetscErrorCode ierr;
976 UserMG *usermg = &simCtx->usermg;
977
978 PetscFunctionBeginUser;
980
981 LOG_ALLOW(GLOBAL, LOG_INFO, "Propagating BC configuration from finest to coarser multigrid levels...\n");
982
983 // Loop from second-finest down to coarsest
984 for (PetscInt level = usermg->mglevels - 2; level >= 0; level--) {
985 for (PetscInt bi = 0; bi < simCtx->block_number; bi++) {
986 UserCtx *user_coarse = &usermg->mgctx[level].user[bi];
987 UserCtx *user_fine = &usermg->mgctx[level + 1].user[bi];
988
989 LOG_ALLOW_SYNC(LOCAL, LOG_DEBUG, "Rank %d: Copying BC config from level %d to level %d, block %d\n",
990 simCtx->rank, level + 1, level, bi);
991
992 // Copy the 6 boundary face configurations
993 for (int face_i = 0; face_i < 6; face_i++) {
994 user_coarse->boundary_faces[face_i].face_id = user_fine->boundary_faces[face_i].face_id;
995 user_coarse->boundary_faces[face_i].mathematical_type = user_fine->boundary_faces[face_i].mathematical_type;
996 user_coarse->boundary_faces[face_i].handler_type = user_fine->boundary_faces[face_i].handler_type;
997
998 // Copy parameter list (deep copy)
999 FreeBC_ParamList(user_coarse->boundary_faces[face_i].params); // Clear any existing
1000 user_coarse->boundary_faces[face_i].params = NULL;
1001
1002 BC_Param **dst_next = &user_coarse->boundary_faces[face_i].params;
1003 for (BC_Param *src = user_fine->boundary_faces[face_i].params; src; src = src->next) {
1004 BC_Param *new_param;
1005 ierr = PetscMalloc1(1, &new_param); CHKERRQ(ierr);
1006 ierr = PetscStrallocpy(src->key, &new_param->key); CHKERRQ(ierr);
1007 ierr = PetscStrallocpy(src->value, &new_param->value); CHKERRQ(ierr);
1008 new_param->next = NULL;
1009 *dst_next = new_param;
1010 dst_next = &new_param->next;
1011 }
1012
1013 // IMPORTANT: Do NOT create handler objects for coarser levels
1014 // Handlers are only needed at finest level for timestepping Apply() calls
1015 user_coarse->boundary_faces[face_i].handler = NULL;
1016 }
1017
1018 // Propagate the particle inlet lookup fields to coarse levels as well.
1019 user_coarse->inletFaceDefined = user_fine->inletFaceDefined;
1020 user_coarse->identifiedInletBCFace = user_fine->identifiedInletBCFace;
1021 }
1022 }
1023
1024 LOG_ALLOW(GLOBAL, LOG_INFO, "BC configuration propagation complete.\n");
1025
1027 PetscFunctionReturn(0);
1028}
void FreeBC_ParamList(BC_Param *head)
Frees an entire linked list of boundary-condition parameters.
Definition io.c:672
#define LOG_ALLOW_SYNC(scope, level, fmt,...)
Synchronized logging macro that checks both the log level and whether the calling function is in the ...
Definition logging.h:253
#define PROFILE_FUNCTION_END
Marks the end of a profiled code block.
Definition logging.h:894
#define PROFILE_FUNCTION_BEGIN
Marks the beginning of a profiled code block (typically a function).
Definition logging.h:885
UserCtx * user
Definition variables.h:729
PetscBool inletFaceDefined
Definition variables.h:1100
PetscMPIInt rank
Definition variables.h:862
PetscInt block_number
Definition variables.h:952
BCFace identifiedInletBCFace
Definition variables.h:1101
struct BC_Param_s * next
Definition variables.h:363
char * key
Definition variables.h:361
UserMG usermg
Definition variables.h:1015
char * value
Definition variables.h:362
BC_Param * params
Definition variables.h:394
PetscInt mglevels
Definition variables.h:736
MGCtx * mgctx
Definition variables.h:739
A node in a linked list for storing key-value parameters from the bcs.dat file.
Definition variables.h:360
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1074
User-level context for managing the entire multigrid hierarchy.
Definition variables.h:735
Here is the call graph for this function:
Here is the caller graph for this function:

◆ BoundarySystem_ExecuteStep()

PetscErrorCode BoundarySystem_ExecuteStep ( UserCtx *  user)

Executes one full boundary condition update cycle for a time step.

Parameters
[in,out]userBlock context whose boundary handlers update target values and fluxes.
Returns
PetscErrorCode 0 on success.

Executes one full boundary condition update cycle for a time step.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/Boundaries.h.

See also
BoundarySystem_ExecuteStep()

Definition at line 1044 of file Boundaries.c.

1045{
1046 PetscErrorCode ierr;
1047 PetscFunctionBeginUser;
1049
1050 LOG_ALLOW(LOCAL, LOG_DEBUG, "Starting.\n");
1051
1052 // =========================================================================
1053 // PRIORITY 0: INLETS
1054 // =========================================================================
1055
1056 PetscReal local_inflow_pre = 0.0;
1057 PetscReal local_inflow_post = 0.0;
1058 PetscReal global_inflow_pre = 0.0;
1059 PetscReal global_inflow_post = 0.0;
1060 PetscInt num_handlers[3] = {0,0,0};
1061
1062 LOG_ALLOW(LOCAL, LOG_TRACE, " (INLETS): Begin.\n");
1063
1064 // Phase 1: PreStep - Preparation (e.g., calculate profiles, read files)
1065 for (int i = 0; i < 6; i++) {
1066 BoundaryCondition *handler = user->boundary_faces[i].handler;
1067 if (!handler || handler->priority != BC_PRIORITY_INLET) continue;
1068 if (!handler->PreStep) continue;
1069
1070 num_handlers[0]++;
1071 BCContext ctx = {
1072 .user = user,
1073 .face_id = (BCFace)i,
1074 .global_inflow_sum = NULL,
1075 .global_outflow_sum = NULL,
1076 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1077 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1078 };
1079
1080 LOG_ALLOW(LOCAL, LOG_TRACE, " PreStep: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1081 ierr = handler->PreStep(handler, &ctx, &local_inflow_pre, NULL); CHKERRQ(ierr);
1082 }
1083
1084 // Optional: Global communication for PreStep (for debugging)
1085 if (local_inflow_pre != 0.0) {
1086 ierr = MPI_Allreduce(&local_inflow_pre, &global_inflow_pre, 1, MPIU_REAL,
1087 MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1088 LOG_ALLOW(GLOBAL, LOG_TRACE, " PreStep predicted flux: %.6e\n", global_inflow_pre);
1089 }
1090
1091 // Phase 2: Apply - Set boundary conditions
1092 for (int i = 0; i < 6; i++) {
1093 BoundaryCondition *handler = user->boundary_faces[i].handler;
1094 if (!handler || handler->priority != BC_PRIORITY_INLET) continue;
1095 if(!handler->Apply) continue; // For example Periodic BCs
1096
1097 num_handlers[1]++;
1098
1099 BCContext ctx = {
1100 .user = user,
1101 .face_id = (BCFace)i,
1102 .global_inflow_sum = NULL,
1103 .global_outflow_sum = NULL,
1104 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1105 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1106 };
1107
1108 LOG_ALLOW(LOCAL, LOG_TRACE, " Apply: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1109 ierr = handler->Apply(handler, &ctx); CHKERRQ(ierr);
1110 }
1111
1112 // Phase 3: PostStep - Measure actual flux
1113 for (int i = 0; i < 6; i++) {
1114 BoundaryCondition *handler = user->boundary_faces[i].handler;
1115 if (!handler || handler->priority != BC_PRIORITY_INLET) continue;
1116 if (!handler->PostStep) continue;
1117
1118 num_handlers[2]++;
1119
1120 BCContext ctx = {
1121 .user = user,
1122 .face_id = (BCFace)i,
1123 .global_inflow_sum = NULL,
1124 .global_outflow_sum = NULL,
1125 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1126 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1127 };
1128
1129 LOG_ALLOW(LOCAL, LOG_TRACE, " PostStep: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1130 ierr = handler->PostStep(handler, &ctx, &local_inflow_post, NULL); CHKERRQ(ierr);
1131 }
1132
1133 // Phase 4: Global communication - Sum flux for other priorities to use
1134 ierr = MPI_Allreduce(&local_inflow_post, &global_inflow_post, 1, MPIU_REAL,
1135 MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1136
1137 // Store for next priority levels
1138 user->simCtx->FluxInSum = global_inflow_post;
1139
1141 " (INLETS): %d Prestep(s), %d Application(s), %d Poststep(s), FluxInSum = %.6e\n",
1142 num_handlers[0],num_handlers[1],num_handlers[2], global_inflow_post);
1143
1144 // =========================================================================
1145 // PRIORITY 1: FARFIELD
1146 // =========================================================================
1147
1148 PetscReal local_farfield_in_pre = 0.0;
1149 PetscReal local_farfield_out_pre = 0.0;
1150 PetscReal local_farfield_in_post = 0.0;
1151 PetscReal local_farfield_out_post = 0.0;
1152 PetscReal global_farfield_in_pre = 0.0;
1153 PetscReal global_farfield_out_pre = 0.0;
1154 PetscReal global_farfield_in_post = 0.0;
1155 PetscReal global_farfield_out_post = 0.0;
1156 memset(num_handlers,0,sizeof(num_handlers));
1157
1158 LOG_ALLOW(LOCAL, LOG_TRACE, " (FARFIELD): Begin.\n");
1159
1160 // Phase 1: PreStep - Analyze flow direction, measure initial flux
1161 for (int i = 0; i < 6; i++) {
1162 BoundaryCondition *handler = user->boundary_faces[i].handler;
1163 if (!handler || handler->priority != BC_PRIORITY_FARFIELD) continue;
1164 if (!handler->PreStep) continue;
1165
1166 num_handlers[0]++;
1167 BCContext ctx = {
1168 .user = user,
1169 .face_id = (BCFace)i,
1170 .global_inflow_sum = &user->simCtx->FluxInSum, // Available from Priority 0
1171 .global_outflow_sum = NULL,
1172 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1173 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1174 };
1175
1176 LOG_ALLOW(LOCAL, LOG_TRACE, " PreStep: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1177 ierr = handler->PreStep(handler, &ctx, &local_farfield_in_pre, &local_farfield_out_pre);
1178 CHKERRQ(ierr);
1179 }
1180
1181 // Phase 2: Global communication (optional, for debugging)
1182 if (local_farfield_in_pre != 0.0 || local_farfield_out_pre != 0.0) {
1183 ierr = MPI_Allreduce(&local_farfield_in_pre, &global_farfield_in_pre, 1, MPIU_REAL,
1184 MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1185 ierr = MPI_Allreduce(&local_farfield_out_pre, &global_farfield_out_pre, 1, MPIU_REAL,
1186 MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1187
1189 " Farfield pre-analysis: In=%.6e, Out=%.6e\n",
1190 global_farfield_in_pre, global_farfield_out_pre);
1191 }
1192
1193 // Phase 3: Apply - Set farfield boundary conditions
1194 for (int i = 0; i < 6; i++) {
1195 BoundaryCondition *handler = user->boundary_faces[i].handler;
1196 if (!handler || handler->priority != BC_PRIORITY_FARFIELD) continue;
1197 if(!handler->Apply) continue; // For example Periodic BCs
1198
1199 num_handlers[1]++;
1200
1201 BCContext ctx = {
1202 .user = user,
1203 .face_id = (BCFace)i,
1204 .global_inflow_sum = &user->simCtx->FluxInSum,
1205 .global_outflow_sum = NULL,
1206 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1207 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1208 };
1209
1210 LOG_ALLOW(LOCAL, LOG_TRACE, " Apply: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1211 ierr = handler->Apply(handler, &ctx); CHKERRQ(ierr);
1212 }
1213
1214 // Phase 4: PostStep - Measure actual farfield fluxes
1215 for (int i = 0; i < 6; i++) {
1216 BoundaryCondition *handler = user->boundary_faces[i].handler;
1217 if (!handler || handler->priority != BC_PRIORITY_FARFIELD) continue;
1218 if (!handler->PostStep) continue;
1219
1220 num_handlers[2]++;
1221
1222 BCContext ctx = {
1223 .user = user,
1224 .face_id = (BCFace)i,
1225 .global_inflow_sum = &user->simCtx->FluxInSum,
1226 .global_outflow_sum = NULL,
1227 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1228 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1229 };
1230
1231 LOG_ALLOW(LOCAL, LOG_TRACE, " PostStep: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1232 ierr = handler->PostStep(handler, &ctx, &local_farfield_in_post, &local_farfield_out_post);
1233 CHKERRQ(ierr);
1234 }
1235
1236 // Phase 5: Global communication - Store for outlet priority
1237 if (num_handlers > 0) {
1238 ierr = MPI_Allreduce(&local_farfield_in_post, &global_farfield_in_post, 1, MPIU_REAL,
1239 MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1240 ierr = MPI_Allreduce(&local_farfield_out_post, &global_farfield_out_post, 1, MPIU_REAL,
1241 MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1242
1243 // Store for outlet handlers to use
1244 user->simCtx->FarFluxInSum = global_farfield_in_post;
1245 user->simCtx->FarFluxOutSum = global_farfield_out_post;
1246
1248 " (FARFIELD): %d Prestep(s), %d Application(s), %d Poststep(s) , InFlux=%.6e, OutFlux=%.6e\n",
1249 num_handlers[0],num_handlers[1],num_handlers[2], global_farfield_in_post, global_farfield_out_post);
1250 } else {
1251 // No farfield handlers - zero out the fluxes
1252 user->simCtx->FarFluxInSum = 0.0;
1253 user->simCtx->FarFluxOutSum = 0.0;
1254 }
1255
1256
1257 // =========================================================================
1258 // PRIORITY 2: WALLS
1259 // =========================================================================
1260
1261 memset(num_handlers,0,sizeof(num_handlers));
1262
1263 LOG_ALLOW(LOCAL, LOG_TRACE, " (WALLS): Begin.\n");
1264
1265 // Phase 1: PreStep - Preparation (usually no-op for walls)
1266 for (int i = 0; i < 6; i++) {
1267 BoundaryCondition *handler = user->boundary_faces[i].handler;
1268 if (!handler || handler->priority != BC_PRIORITY_WALL) continue;
1269 if (!handler->PreStep) continue;
1270
1271 num_handlers[0]++;
1272 BCContext ctx = {
1273 .user = user,
1274 .face_id = (BCFace)i,
1275 .global_inflow_sum = &user->simCtx->FluxInSum,
1276 .global_outflow_sum = NULL,
1277 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1278 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1279 };
1280
1281 LOG_ALLOW(LOCAL, LOG_TRACE, " PreStep: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1282 ierr = handler->PreStep(handler, &ctx, NULL, NULL); CHKERRQ(ierr);
1283 }
1284
1285 // No global communication needed for walls
1286
1287 // Phase 2: Apply - Set boundary conditions
1288 for (int i = 0; i < 6; i++) {
1289 BoundaryCondition *handler = user->boundary_faces[i].handler;
1290 if (!handler || handler->priority != BC_PRIORITY_WALL) continue;
1291 if(!handler->Apply) continue; // For example Periodic BCs
1292
1293 num_handlers[1]++;
1294
1295 BCContext ctx = {
1296 .user = user,
1297 .face_id = (BCFace)i,
1298 .global_inflow_sum = &user->simCtx->FluxInSum,
1299 .global_outflow_sum = NULL,
1300 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1301 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1302 };
1303
1304 LOG_ALLOW(LOCAL, LOG_TRACE, " Apply: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1305 ierr = handler->Apply(handler, &ctx); CHKERRQ(ierr);
1306 }
1307
1308 // Phase 3: PostStep - Post-application processing (usually no-op for walls)
1309 for (int i = 0; i < 6; i++) {
1310 BoundaryCondition *handler = user->boundary_faces[i].handler;
1311 if (!handler || handler->priority != BC_PRIORITY_WALL) continue;
1312 if (!handler->PostStep) continue;
1313
1314 num_handlers[2]++;
1315
1316 BCContext ctx = {
1317 .user = user,
1318 .face_id = (BCFace)i,
1319 .global_inflow_sum = &user->simCtx->FluxInSum,
1320 .global_outflow_sum = NULL,
1321 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1322 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1323 };
1324
1325 LOG_ALLOW(LOCAL, LOG_TRACE, " PostStep: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1326 ierr = handler->PostStep(handler, &ctx, NULL, NULL); CHKERRQ(ierr);
1327 }
1328
1329 // No global communication needed for walls
1330
1331 LOG_ALLOW(GLOBAL, LOG_INFO, " (WALLS): %d Prestep(s), %d Application(s), %d Poststep(s) applied.\n",
1332 num_handlers[0],num_handlers[1],num_handlers[2]);
1333
1334
1335 // =========================================================================
1336 // PRIORITY 3: OUTLETS
1337 // =========================================================================
1338
1339 PetscReal local_outflow_pre = 0.0;
1340 PetscReal local_outflow_post = 0.0;
1341 PetscReal global_outflow_pre = 0.0;
1342 PetscReal global_outflow_post = 0.0;
1343 memset(num_handlers,0,sizeof(num_handlers));
1344
1345 LOG_ALLOW(LOCAL, LOG_TRACE, " (OUTLETS): Begin.\n");
1346
1347 // Phase 1: PreStep - Measure uncorrected outflow (from ucat)
1348 for (int i = 0; i < 6; i++) {
1349 BoundaryCondition *handler = user->boundary_faces[i].handler;
1350 if (!handler || handler->priority != BC_PRIORITY_OUTLET) continue;
1351 if (!handler->PreStep) continue;
1352
1353 num_handlers[0]++;
1354 BCContext ctx = {
1355 .user = user,
1356 .face_id = (BCFace)i,
1357 .global_inflow_sum = &user->simCtx->FluxInSum, // From Priority 0
1358 .global_outflow_sum = NULL,
1359 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1360 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1361 };
1362
1363 LOG_ALLOW(LOCAL, LOG_TRACE, " PreStep: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1364 ierr = handler->PreStep(handler, &ctx, NULL, &local_outflow_pre); CHKERRQ(ierr);
1365 }
1366
1367 // Phase 2: Global communication - Get uncorrected outflow sum
1368 ierr = MPI_Allreduce(&local_outflow_pre, &global_outflow_pre, 1, MPIU_REAL,
1369 MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1370
1371 // Calculate total inflow (inlet + farfield inflow)
1372 PetscReal total_inflow = user->simCtx->FluxInSum + user->simCtx->FarFluxInSum;
1373
1375 " Uncorrected outflow: %.6e, Total inflow: %.6e (Inlet: %.6e + Farfield: %.6e)\n",
1376 global_outflow_pre, total_inflow, user->simCtx->FluxInSum,
1377 user->simCtx->FarFluxInSum);
1378
1379 // Phase 3: Apply - Set corrected boundary conditions
1380 for (int i = 0; i < 6; i++) {
1381 BoundaryCondition *handler = user->boundary_faces[i].handler;
1382 if (!handler || handler->priority != BC_PRIORITY_OUTLET) continue;
1383 if(!handler->Apply) continue; // For example Periodic BCs
1384
1385 num_handlers[1]++;
1386
1387 BCContext ctx = {
1388 .user = user,
1389 .face_id = (BCFace)i,
1390 .global_inflow_sum = &user->simCtx->FluxInSum, // From Priority 0
1391 .global_outflow_sum = &global_outflow_pre, // From PreStep above
1392 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1393 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1394 };
1395
1396 LOG_ALLOW(LOCAL, LOG_TRACE, " Apply: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1397 ierr = handler->Apply(handler, &ctx); CHKERRQ(ierr);
1398 }
1399
1400 // Phase 4: PostStep - Measure corrected outflow (verification)
1401 for (int i = 0; i < 6; i++) {
1402 BoundaryCondition *handler = user->boundary_faces[i].handler;
1403 if (!handler || handler->priority != BC_PRIORITY_OUTLET) continue;
1404 if (!handler->PostStep) continue;
1405
1406 num_handlers[2]++;
1407
1408 BCContext ctx = {
1409 .user = user,
1410 .face_id = (BCFace)i,
1411 .global_inflow_sum = &user->simCtx->FluxInSum,
1412 .global_outflow_sum = &global_outflow_pre,
1413 .global_farfield_inflow_sum = &user->simCtx->FarFluxInSum,
1414 .global_farfield_outflow_sum = &user->simCtx->FarFluxOutSum
1415 };
1416
1417 LOG_ALLOW(LOCAL, LOG_TRACE, " PostStep: Face %d (%s)\n", i, BCFaceToString((BCFace)i));
1418 ierr = handler->PostStep(handler, &ctx, NULL, &local_outflow_post); CHKERRQ(ierr);
1419 }
1420
1421 // Phase 5: Global communication - Verify conservation
1422 ierr = MPI_Allreduce(&local_outflow_post, &global_outflow_post, 1, MPIU_REAL,
1423 MPI_SUM, PETSC_COMM_WORLD); CHKERRQ(ierr);
1424
1425 // Store for global reporting.
1426 user->simCtx->FluxOutSum = global_outflow_post;
1427
1428 // Conservation check (compare total outflow vs total inflow)
1429 PetscReal total_outflow = global_outflow_post + user->simCtx->FarFluxOutSum;
1430 PetscReal flux_error = PetscAbsReal(total_outflow - total_inflow);
1431 PetscReal relative_error = (total_inflow > 1e-16) ?
1432 flux_error / total_inflow : flux_error;
1433
1435 " (OUTLETS): %d Prestep(s), %d Application(s), %d Poststep(s), FluxOutSum = %.6e\n",
1436 num_handlers[0],num_handlers[1],num_handlers[2], global_outflow_post);
1438 " Conservation: Total In=%.6e, Total Out=%.6e, Error=%.3e (%.2e)%%)\n",
1439 total_inflow, total_outflow, flux_error, relative_error * 100.0);
1440
1441 if (relative_error > 1e-6) {
1443 " WARNING: Large mass conservation error (%.2e%%)!\n",
1444 relative_error * 100.0);
1445 }
1446
1447
1448 LOG_ALLOW(LOCAL, LOG_VERBOSE, "Complete.\n");
1449
1451 PetscFunctionReturn(0);
1452}
@ LOG_TRACE
Very fine-grained tracing information for in-depth debugging.
Definition logging.h:33
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
@ LOG_VERBOSE
Extremely detailed logs, typically for development use only.
Definition logging.h:34
@ BC_PRIORITY_OUTLET
Definition variables.h:352
@ BC_PRIORITY_FARFIELD
Definition variables.h:350
@ BC_PRIORITY_WALL
Definition variables.h:351
@ BC_PRIORITY_INLET
Definition variables.h:349
Here is the call graph for this function:
Here is the caller graph for this function:

◆ BoundarySystem_RefreshUbcs()

PetscErrorCode BoundarySystem_RefreshUbcs ( UserCtx *  user)

(Private) A lightweight execution engine that calls the UpdateUbcs() method on all relevant handlers.

This function's sole purpose is to re-evaluate the target boundary values (ubcs) for flow-dependent boundary conditions (e.g., Symmetry, Outlets) after the interior velocity field has changed, such as after the projection step.

It operates based on a "pull" model: it iterates through all boundary handlers and executes their UpdateUbcs method only if the handler has provided one. This makes the system extensible, as new flow-dependent handlers can be added without changing this engine. Handlers for fixed boundary conditions (e.g., a wall with a constant velocity) will have their UpdateUbcs pointer set to NULL and will be skipped automatically.

Note
This function is a critical part of the post-projection refresh. It intentionally does NOT modify ucont and does NOT perform flux balancing.
Parameters
userThe main UserCtx struct.
Returns
PetscErrorCode 0 on success.

(Private) A lightweight execution engine that calls the UpdateUbcs() method on all relevant handlers.

Local to this translation unit.

Definition at line 1466 of file Boundaries.c.

1467{
1468 PetscErrorCode ierr;
1469 PetscFunctionBeginUser;
1470
1471 LOG_ALLOW(GLOBAL, LOG_TRACE, "Refreshing `ubcs` targets for flow-dependent boundaries...\n");
1472
1473 // Loop through all 6 faces of the domain
1474 for (int i = 0; i < 6; i++) {
1475 BoundaryCondition *handler = user->boundary_faces[i].handler;
1476
1477 // THE FILTER:
1478 // This is the core logic. We only act if a handler exists for the face
1479 // AND that handler has explicitly implemented the `UpdateUbcs` method.
1480 if (handler && handler->UpdateUbcs) {
1481
1482 const char *face_name = BCFaceToString((BCFace)i);
1483 LOG_ALLOW(LOCAL, LOG_TRACE, " Calling UpdateUbcs() for handler on Face %s.\n", face_name);
1484
1485 // Prepare the context. For this refresh step, we don't need to pass flux sums.
1486 BCContext ctx = {
1487 .user = user,
1488 .face_id = (BCFace)i,
1489 .global_inflow_sum = NULL,
1490 .global_outflow_sum = NULL,
1491 .global_farfield_inflow_sum = NULL,
1492 .global_farfield_outflow_sum = NULL
1493 };
1494
1495 // Call the handler's specific UpdateUbcs function pointer.
1496 ierr = handler->UpdateUbcs(handler, &ctx); CHKERRQ(ierr);
1497 }
1498 }
1499
1500 PetscFunctionReturn(0);
1501}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ BoundarySystem_Destroy()

PetscErrorCode BoundarySystem_Destroy ( UserCtx *  user)

Cleans up and destroys all boundary system resources.

Parameters
[in,out]userBlock context whose boundary handlers and temporary state are released.
Returns
PetscErrorCode 0 on success.

Cleans up and destroys all boundary system resources.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/Boundaries.h.

See also
BoundarySystem_Destroy()

Definition at line 1516 of file Boundaries.c.

1517{
1518 PetscErrorCode ierr;
1519 PetscFunctionBeginUser;
1520
1521
1522
1523 LOG_ALLOW(GLOBAL, LOG_INFO, "Starting destruction of all boundary handlers. \n");
1524
1525 for (int i = 0; i < 6; i++) {
1526 BoundaryFaceConfig *face_cfg = &user->boundary_faces[i];
1527 const char *face_name = BCFaceToString(face_cfg->face_id);
1528
1529 // --- Step 1: Free the parameter linked list associated with this face ---
1530 if (face_cfg->params) {
1531 LOG_ALLOW(LOCAL, LOG_DEBUG, " Freeing parameter list for Face %d (%s). \n", i, face_name);
1532 FreeBC_ParamList(face_cfg->params);
1533 face_cfg->params = NULL; // Good practice to nullify dangling pointers
1534 }
1535
1536 // --- Step 2: Destroy the handler object itself ---
1537 if (face_cfg->handler) {
1538 const char *handler_name = BCHandlerTypeToString(face_cfg->handler->type);
1539 LOG_ALLOW(LOCAL, LOG_DEBUG, " Destroying handler '%s' on Face %d (%s).\n", handler_name, i, face_name);
1540
1541 // Call the handler's specific cleanup function first, if it exists.
1542 // This will free any memory stored in the handler's private `data` pointer.
1543 if (face_cfg->handler->Destroy) {
1544 ierr = face_cfg->handler->Destroy(face_cfg->handler); CHKERRQ(ierr);
1545 }
1546
1547 // Finally, free the generic BoundaryCondition object itself.
1548 ierr = PetscFree(face_cfg->handler); CHKERRQ(ierr);
1549 face_cfg->handler = NULL;
1550 }
1551 }
1552
1553 LOG_ALLOW(GLOBAL, LOG_INFO, "Destruction complete.\n");
1554 PetscFunctionReturn(0);
1555}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ CanRankServiceInletFace()

PetscErrorCode CanRankServiceInletFace ( UserCtx *  user,
const DMDALocalInfo *  info,
PetscInt  IM_nodes_global,
PetscInt  JM_nodes_global,
PetscInt  KM_nodes_global,
PetscBool *  can_service_inlet_out 
)

Determines if the current MPI rank owns any part of the globally defined inlet face, making it responsible for placing particles on that portion of the surface.

The determination is based on the rank's owned nodes (from DMDALocalInfo) and the global node counts, in conjunction with the user->identifiedInletBCFace. A rank can service an inlet face if it owns the cells adjacent to that global boundary and has a non-zero extent (owns cells) in the tangential dimensions of that face.

Parameters
userPointer to the UserCtx structure, containing identifiedInletBCFace.
infoPointer to the DMDALocalInfo for the current rank's DA (node-based).
IM_nodes_globalGlobal number of nodes in the I-direction (e.g., user->IM + 1 if user->IM is cell count).
JM_nodes_globalGlobal number of nodes in the J-direction.
KM_nodes_globalGlobal number of nodes in the K-direction.
[out]can_service_inlet_outPointer to a PetscBool; set to PETSC_TRUE if the rank services (part of) the inlet, PETSC_FALSE otherwise.
Returns
PetscErrorCode 0 on success, non-zero on failure.

Determines if the current MPI rank owns any part of the globally defined inlet face, making it responsible for placing particles on that portion of the surface.

Local to this translation unit.

Definition at line 11 of file Boundaries.c.

14{
15 PetscErrorCode ierr;
16 PetscMPIInt rank_for_logging; // For detailed debugging logs
17 PetscFunctionBeginUser;
19
20 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank_for_logging); CHKERRQ(ierr);
21
22 *can_service_inlet_out = PETSC_FALSE; // Default to no service
23
24 if (!user->inletFaceDefined) {
25 LOG_ALLOW(LOCAL, LOG_DEBUG, "[Rank %d]: Inlet face not defined in user context. Cannot service.\n", rank_for_logging);
27 PetscFunctionReturn(0);
28 }
29
30 // Get the range of cells owned by this rank in each dimension
31 PetscInt owned_start_cell_i, num_owned_cells_on_rank_i;
32 PetscInt owned_start_cell_j, num_owned_cells_on_rank_j;
33 PetscInt owned_start_cell_k, num_owned_cells_on_rank_k;
34
35 ierr = GetOwnedCellRange(info, 0, &owned_start_cell_i, &num_owned_cells_on_rank_i); CHKERRQ(ierr);
36 ierr = GetOwnedCellRange(info, 1, &owned_start_cell_j, &num_owned_cells_on_rank_j); CHKERRQ(ierr);
37 ierr = GetOwnedCellRange(info, 2, &owned_start_cell_k, &num_owned_cells_on_rank_k); CHKERRQ(ierr);
38
39 // Determine the global index of the last cell (0-indexed) in each direction.
40 // Example: If IM_nodes_global = 11 (nodes 0-10), there are 10 cells (0-9). Last cell index is 9.
41 // Formula: global_nodes - 1 (num cells) - 1 (0-indexed) = global_nodes - 2.
42 PetscInt last_global_cell_idx_i = (IM_nodes_global > 1) ? (IM_nodes_global - 2) : -1; // -1 if 0 or 1 node (i.e., 0 cells)
43 PetscInt last_global_cell_idx_j = (JM_nodes_global > 1) ? (JM_nodes_global - 2) : -1;
44 PetscInt last_global_cell_idx_k = (KM_nodes_global > 1) ? (KM_nodes_global - 2) : -1;
45
46 switch (user->identifiedInletBCFace) {
47 case BC_FACE_NEG_X: // Inlet on the global I-minimum face (face of cell C_i=0)
48 // Rank services if its first owned node is global node 0 (info->xs == 0),
49 // and it owns cells in I, J, and K directions.
50 if (info->xs == 0 && num_owned_cells_on_rank_i > 0 &&
51 num_owned_cells_on_rank_j > 0 && num_owned_cells_on_rank_k > 0) {
52 *can_service_inlet_out = PETSC_TRUE;
53 }
54 break;
55 case BC_FACE_POS_X: // Inlet on the global I-maximum face (face of cell C_i=last_global_cell_idx_i)
56 // Rank services if it owns the last cell in I-direction,
57 // and has extent in J and K.
58 if (last_global_cell_idx_i >= 0 && /* Check for valid global domain */
59 (owned_start_cell_i + num_owned_cells_on_rank_i - 1) == last_global_cell_idx_i && /* Rank's last cell is the global last cell */
60 num_owned_cells_on_rank_j > 0 && num_owned_cells_on_rank_k > 0) {
61 *can_service_inlet_out = PETSC_TRUE;
62 }
63 break;
64 case BC_FACE_NEG_Y:
65 if (info->ys == 0 && num_owned_cells_on_rank_j > 0 &&
66 num_owned_cells_on_rank_i > 0 && num_owned_cells_on_rank_k > 0) {
67 *can_service_inlet_out = PETSC_TRUE;
68 }
69 break;
70 case BC_FACE_POS_Y:
71 if (last_global_cell_idx_j >= 0 &&
72 (owned_start_cell_j + num_owned_cells_on_rank_j - 1) == last_global_cell_idx_j &&
73 num_owned_cells_on_rank_i > 0 && num_owned_cells_on_rank_k > 0) {
74 *can_service_inlet_out = PETSC_TRUE;
75 }
76 break;
77 case BC_FACE_NEG_Z:
78 if (info->zs == 0 && num_owned_cells_on_rank_k > 0 &&
79 num_owned_cells_on_rank_i > 0 && num_owned_cells_on_rank_j > 0) {
80 *can_service_inlet_out = PETSC_TRUE;
81 }
82 break;
83 case BC_FACE_POS_Z:
84 if (last_global_cell_idx_k >= 0 &&
85 (owned_start_cell_k + num_owned_cells_on_rank_k - 1) == last_global_cell_idx_k &&
86 num_owned_cells_on_rank_i > 0 && num_owned_cells_on_rank_j > 0) {
87 *can_service_inlet_out = PETSC_TRUE;
88 }
89 break;
90 default:
91 LOG_ALLOW(LOCAL, LOG_WARNING, "[Rank %d]: Unknown inlet face %s.\n", rank_for_logging, BCFaceToString((BCFace)user->identifiedInletBCFace));
92 break;
93 }
94
96 "[Rank %d] Check Service for Inlet %s:\n"
97 " - Local Domain: starts at cell (%d,%d,%d), has (%d,%d,%d) cells.\n"
98 " - Global Domain: has (%d,%d,%d) nodes, so last cell is (%d,%d,%d).\n",
99 rank_for_logging,
101 owned_start_cell_i, owned_start_cell_j, owned_start_cell_k,
102 num_owned_cells_on_rank_i, num_owned_cells_on_rank_j, num_owned_cells_on_rank_k,
103 IM_nodes_global, JM_nodes_global, KM_nodes_global,
104 last_global_cell_idx_i, last_global_cell_idx_j, last_global_cell_idx_k);
105
106 LOG_ALLOW(LOCAL, LOG_INFO,"[Rank %d] Inlet Face %s Service Check Result: %s | Owned Cells (I,J,K): (%d,%d,%d) | Starts at Cell (%d,%d,%d)\n",
107 rank_for_logging,
109 (*can_service_inlet_out) ? "CAN SERVICE" : "CANNOT SERVICE",
110 num_owned_cells_on_rank_i, num_owned_cells_on_rank_j, num_owned_cells_on_rank_k,
111 owned_start_cell_i, owned_start_cell_j, owned_start_cell_k);
112
114
115 PetscFunctionReturn(0);
116}
PetscErrorCode GetOwnedCellRange(const DMDALocalInfo *info_nodes, PetscInt dim, PetscInt *xs_cell_global_out, PetscInt *xm_cell_local_out)
Determines the global starting index and number of CELLS owned by the current processor in a specifie...
Definition setup.c:2936
Here is the call graph for this function:
Here is the caller graph for this function:

◆ CanRankServiceFace()

PetscErrorCode CanRankServiceFace ( const DMDALocalInfo *  info,
PetscInt  IM_nodes_global,
PetscInt  JM_nodes_global,
PetscInt  KM_nodes_global,
BCFace  face_id,
PetscBool *  can_service_out 
)

Determines if the current MPI rank owns any part of a specified global face.

This function is a general utility for parallel boundary operations. It checks if the local domain of the current MPI rank is adjacent to a specified global boundary face. A rank "services" a face if it owns the cells adjacent to that global boundary and has a non-zero extent (i.e., owns at least one cell) in the tangential dimensions of that face.

Parameters
infoPointer to the DMDALocalInfo for the current rank's DA.
IM_nodes_globalGlobal number of nodes in the I-direction (e.g., user->IM + 1 if user->IM is cell count).
JM_nodes_globalGlobal number of nodes in the J-direction.
KM_nodes_globalGlobal number of nodes in the K-direction.
face_idThe specific global face (e.g., BC_FACE_NEG_Z) to check.
[out]can_service_outPointer to a PetscBool; set to PETSC_TRUE if the rank services the face, PETSC_FALSE otherwise.
Returns
PetscErrorCode 0 on success.

Determines if the current MPI rank owns any part of a specified global face.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/Boundaries.h.

See also
CanRankServiceFace()

Definition at line 127 of file Boundaries.c.

129{
130 PetscErrorCode ierr;
131 PetscMPIInt rank_for_logging;
132 PetscFunctionBeginUser;
133
135
136 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank_for_logging); CHKERRQ(ierr);
137
138 *can_service_out = PETSC_FALSE; // Default to no service
139
140 // Get the range of cells owned by this rank
141 PetscInt owned_start_cell_i, num_owned_cells_on_rank_i;
142 PetscInt owned_start_cell_j, num_owned_cells_on_rank_j;
143 PetscInt owned_start_cell_k, num_owned_cells_on_rank_k;
144 ierr = GetOwnedCellRange(info, 0, &owned_start_cell_i, &num_owned_cells_on_rank_i); CHKERRQ(ierr);
145 ierr = GetOwnedCellRange(info, 1, &owned_start_cell_j, &num_owned_cells_on_rank_j); CHKERRQ(ierr);
146 ierr = GetOwnedCellRange(info, 2, &owned_start_cell_k, &num_owned_cells_on_rank_k); CHKERRQ(ierr);
147
148 // Determine the global index of the last cell (0-indexed) in each direction.
149 PetscInt last_global_cell_idx_i = (IM_nodes_global > 1) ? (IM_nodes_global - 2) : -1;
150 PetscInt last_global_cell_idx_j = (JM_nodes_global > 1) ? (JM_nodes_global - 2) : -1;
151 PetscInt last_global_cell_idx_k = (KM_nodes_global > 1) ? (KM_nodes_global - 2) : -1;
152
153 switch (face_id) {
154 case BC_FACE_NEG_X:
155 if (info->xs == 0 && num_owned_cells_on_rank_i > 0 &&
156 num_owned_cells_on_rank_j > 0 && num_owned_cells_on_rank_k > 0) {
157 *can_service_out = PETSC_TRUE;
158 }
159 break;
160 case BC_FACE_POS_X:
161 if (last_global_cell_idx_i >= 0 &&
162 (owned_start_cell_i + num_owned_cells_on_rank_i - 1) == last_global_cell_idx_i &&
163 num_owned_cells_on_rank_j > 0 && num_owned_cells_on_rank_k > 0) {
164 *can_service_out = PETSC_TRUE;
165 }
166 break;
167 case BC_FACE_NEG_Y:
168 if (info->ys == 0 && num_owned_cells_on_rank_j > 0 &&
169 num_owned_cells_on_rank_i > 0 && num_owned_cells_on_rank_k > 0) {
170 *can_service_out = PETSC_TRUE;
171 }
172 break;
173 case BC_FACE_POS_Y:
174 if (last_global_cell_idx_j >= 0 &&
175 (owned_start_cell_j + num_owned_cells_on_rank_j - 1) == last_global_cell_idx_j &&
176 num_owned_cells_on_rank_i > 0 && num_owned_cells_on_rank_k > 0) {
177 *can_service_out = PETSC_TRUE;
178 }
179 break;
180 case BC_FACE_NEG_Z:
181 if (info->zs == 0 && num_owned_cells_on_rank_k > 0 &&
182 num_owned_cells_on_rank_i > 0 && num_owned_cells_on_rank_j > 0) {
183 *can_service_out = PETSC_TRUE;
184 }
185 break;
186 case BC_FACE_POS_Z:
187 if (last_global_cell_idx_k >= 0 &&
188 (owned_start_cell_k + num_owned_cells_on_rank_k - 1) == last_global_cell_idx_k &&
189 num_owned_cells_on_rank_i > 0 && num_owned_cells_on_rank_j > 0) {
190 *can_service_out = PETSC_TRUE;
191 }
192 break;
193 default:
194 LOG_ALLOW(LOCAL, LOG_WARNING, "Rank %d: Unknown face enum %d. \n", rank_for_logging, face_id);
195 break;
196 }
197
198 LOG_ALLOW(LOCAL, LOG_DEBUG, "Rank %d check for face %s: Result=%s. \n",
199 rank_for_logging, BCFaceToString((BCFace)face_id), (*can_service_out ? "TRUE" : "FALSE"));
200
202
203 PetscFunctionReturn(0);
204}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetDeterministicFaceGridLocation()

PetscErrorCode GetDeterministicFaceGridLocation ( UserCtx *  user,
const DMDALocalInfo *  info,
PetscInt  xs_gnode_rank,
PetscInt  ys_gnode_rank,
PetscInt  zs_gnode_rank,
PetscInt  IM_cells_global,
PetscInt  JM_cells_global,
PetscInt  KM_cells_global,
PetscInt64  particle_global_id,
PetscInt *  ci_metric_lnode_out,
PetscInt *  cj_metric_lnode_out,
PetscInt *  ck_metric_lnode_out,
PetscReal *  xi_metric_logic_out,
PetscReal *  eta_metric_logic_out,
PetscReal *  zta_metric_logic_out,
PetscBool *  placement_successful_out 
)

Places particles in a deterministic grid/raster pattern on a specified domain face.

This function creates a set of equidistant, parallel lines of particles near the four edges of the face specified by user->identifiedInletBCFace. The number of lines drawn from each edge is hardcoded within this function (default is 2). For example, if grid_layers=2 on face BC_FACE_NEG_X, the function will create particle lines at:

  • y ~ 0*dy, y ~ 1*dy (parallel to the Z-axis, starting from the J=0 edge)
  • y ~ y_max, y ~ y_max-dy (parallel to the Z-axis, starting from the J=max edge)
  • z ~ 0*dz, z ~ 1*dz (parallel to the Y-axis, starting from the K=0 edge)
  • z ~ z_max, z ~ z_max-dz (parallel to the Y-axis, starting from the K=max edge) The particle's final position is set just inside the target cell face to ensure it is correctly located. The total number of particles (simCtx->np) is distributed as evenly as possible among all generated lines. The function includes extensive validation to stop with an error if the requested grid placement is geometrically impossible (e.g., in a 2D domain or if layers would overlap). It also issues warnings for non-fatal but potentially unintended configurations.
Parameters
userInlet-boundary context that defines the target face and grid layers.
infoLocal ownership and ghost-range information.
xs_gnode_rankGlobal xi node index at this rank's owned lower corner.
ys_gnode_rankGlobal eta node index at this rank's owned lower corner.
zs_gnode_rankGlobal zeta node index at this rank's owned lower corner.
IM_cells_globalGlobal number of xi cells.
JM_cells_globalGlobal number of eta cells.
KM_cells_globalGlobal number of zeta cells.
particle_global_idGlobal particle ordinal used for deterministic placement.
[out]ci_metric_lnode_outLocal xi metric-node index of the chosen cell.
[out]cj_metric_lnode_outLocal eta metric-node index of the chosen cell.
[out]ck_metric_lnode_outLocal zeta metric-node index of the chosen cell.
[out]xi_metric_logic_outLogical xi coordinate within the chosen cell.
[out]eta_metric_logic_outLogical eta coordinate within the chosen cell.
[out]zta_metric_logic_outLogical zeta coordinate within the chosen cell.
[out]placement_successful_outPETSC_TRUE when this rank owns a valid placement.
Returns
PetscErrorCode 0 on success.

Places particles in a deterministic grid/raster pattern on a specified domain face.

Local to this translation unit.

Definition at line 213 of file Boundaries.c.

221{
222 SimCtx *simCtx = user->simCtx;
223 PetscReal global_logic_i = 0.0, global_logic_j = 0.0, global_logic_k = 0.0;
224 PetscErrorCode ierr;
225 PetscMPIInt rank_for_logging;
226
227 PetscFunctionBeginUser;
228 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank_for_logging); CHKERRQ(ierr);
229
230 *placement_successful_out = PETSC_FALSE; // Default to failure
231
232 // --- Step 1: Configuration and Input Validation ---
233
234 // *** Hardcoded number of grid layers. Change this value to alter the pattern. ***
235 const PetscInt grid_layers = 2;
236
238 "[Rank %d] Placing particle %lld on face %s with grid_layers=%d in global domain (%d,%d,%d) cells.\n",
239 rank_for_logging, (long long)particle_global_id, BCFaceToString(user->identifiedInletBCFace), grid_layers,
240 IM_cells_global, JM_cells_global, KM_cells_global);
241
242 const char *face_name = BCFaceToString(user->identifiedInletBCFace);
243
244 // Fatal Error Checks: Ensure the requested grid is geometrically possible.
245 // The total layers from opposite faces (2 * grid_layers) must be less than the domain size.
246 switch (user->identifiedInletBCFace) {
247 case BC_FACE_NEG_X: case BC_FACE_POS_X:
248 if (JM_cells_global <= 1 || KM_cells_global <= 1) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Cannot place grid on face %s for a 2D/1D domain (J-cells=%d, K-cells=%d).", face_name, JM_cells_global, KM_cells_global);
249 if (2 * grid_layers >= JM_cells_global || 2 * grid_layers >= KM_cells_global) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Grid layers (%d) from opposing J/K faces would overlap in this domain (J-cells=%d, K-cells=%d).", grid_layers, JM_cells_global, KM_cells_global);
250 break;
251 case BC_FACE_NEG_Y: case BC_FACE_POS_Y:
252 if (IM_cells_global <= 1 || KM_cells_global <= 1) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Cannot place grid on face %s for a 2D/1D domain (I-cells=%d, K-cells=%d).", face_name, IM_cells_global, KM_cells_global);
253 if (2 * grid_layers >= IM_cells_global || 2 * grid_layers >= KM_cells_global) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Grid layers (%d) from opposing I/K faces would overlap in this domain (I-cells=%d, K-cells=%d).", grid_layers, IM_cells_global, KM_cells_global);
254 break;
255 case BC_FACE_NEG_Z: case BC_FACE_POS_Z:
256 if (IM_cells_global <= 1 || JM_cells_global <= 1) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Cannot place grid on face %s for a 2D/1D domain (I-cells=%d, J-cells=%d).", face_name, IM_cells_global, JM_cells_global);
257 if (2 * grid_layers >= IM_cells_global || 2 * grid_layers >= JM_cells_global) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Grid layers (%d) from opposing I/J faces would overlap in this domain (I-cells=%d, J-cells=%d).", grid_layers, IM_cells_global, JM_cells_global);
258 break;
259 default: SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Invalid identifiedInletBCFace specified: %d", user->identifiedInletBCFace);
260 }
261
262 const PetscInt num_lines_total = 4 * grid_layers;
263 if (simCtx->np < num_lines_total) {
264 LOG_ALLOW(GLOBAL, LOG_WARNING, "Warning: Total particle count (%lld) is less than the number of grid lines requested (%d). Some lines may be empty.\n", (long long)simCtx->np, num_lines_total);
265 }
266 if (simCtx->np > 0 && simCtx->np % num_lines_total != 0) {
267 LOG_ALLOW(GLOBAL, LOG_WARNING, "Warning: Total particle count (%lld) is not evenly divisible by the number of grid lines (%d). Distribution will be uneven.\n", (long long)simCtx->np, num_lines_total);
268 }
269
270 // --- Step 2: Map global particle ID to a line and a point on that line ---
271 if (simCtx->np == 0) PetscFunctionReturn(0); // Nothing to do
272
273 LOG_ALLOW(LOCAL, LOG_TRACE, "[Rank %d] Distributing %lld particles over %d lines on face %s.\n",
274 rank_for_logging, (long long)simCtx->np, num_lines_total, face_name);
275
276 const PetscInt points_per_line = PetscMax(1, simCtx->np / num_lines_total);
277 PetscInt line_index = particle_global_id / points_per_line;
278 PetscInt point_index_on_line = particle_global_id % points_per_line;
279 line_index = PetscMin(line_index, num_lines_total - 1); // Clamp to handle uneven division
280
281 // Decode the line_index into an edge group (0-3) and a layer within that group (0 to grid_layers-1)
282 const PetscInt edge_group = line_index / grid_layers;
283 const PetscInt layer_index = line_index % grid_layers;
284
285 // --- Step 3: Calculate placement coordinates based on the decoded indices ---
286 const PetscReal layer_spacing_norm_i = (IM_cells_global > 0) ? 1.0 / (PetscReal)IM_cells_global : 0.0;
287 const PetscReal layer_spacing_norm_j = (JM_cells_global > 0) ? 1.0 / (PetscReal)JM_cells_global : 0.0;
288 const PetscReal layer_spacing_norm_k = (KM_cells_global > 0) ? 1.0 / (PetscReal)KM_cells_global : 0.0;
289
290 // Grid-aware epsilon: scale with minimum cell size to keep particles away from rank boundaries
291 const PetscReal min_layer_spacing = PetscMin(layer_spacing_norm_i, PetscMin(layer_spacing_norm_j, layer_spacing_norm_k));
292 const PetscReal epsilon = 0.5 * min_layer_spacing; // Keep particles 10% of cell width from boundaries
293
294 PetscReal variable_coord; // The coordinate that varies along a line
295 if (points_per_line <= 1) {
296 variable_coord = 0.5; // Place single point in the middle
297 } else {
298 variable_coord = ((PetscReal)point_index_on_line + 0.5)/ (PetscReal)(points_per_line);
299 }
300 variable_coord = PetscMin(1.0 - epsilon, PetscMax(epsilon, variable_coord)); // Clamp within [eps, 1-eps]
301
302 // Main logic switch to determine the three global logical coordinates
303 switch (user->identifiedInletBCFace) {
304 case BC_FACE_NEG_X:
305 global_logic_i = 0.5 * layer_spacing_norm_i; // Place near the face, in the middle of the first cell
306 if (edge_group == 0) { global_logic_j = (PetscReal)layer_index * layer_spacing_norm_j + epsilon; global_logic_k = variable_coord; }
307 else if (edge_group == 1) { global_logic_j = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_j) - epsilon; global_logic_k = variable_coord; }
308 else if (edge_group == 2) { global_logic_k = (PetscReal)layer_index * layer_spacing_norm_k + epsilon; global_logic_j = variable_coord; }
309 else /* edge_group == 3 */ { global_logic_k = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_k) - epsilon; global_logic_j = variable_coord; }
310 break;
311 case BC_FACE_POS_X:
312 global_logic_i = 1.0 - (0.5 * layer_spacing_norm_i); // Place near the face, in the middle of the last cell
313 if (edge_group == 0) { global_logic_j = (PetscReal)layer_index * layer_spacing_norm_j + epsilon; global_logic_k = variable_coord; }
314 else if (edge_group == 1) { global_logic_j = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_j) - epsilon; global_logic_k = variable_coord; }
315 else if (edge_group == 2) { global_logic_k = (PetscReal)layer_index * layer_spacing_norm_k + epsilon; global_logic_j = variable_coord; }
316 else /* edge_group == 3 */ { global_logic_k = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_k) - epsilon; global_logic_j = variable_coord; }
317 break;
318 case BC_FACE_NEG_Y:
319 global_logic_j = 0.5 * layer_spacing_norm_j;
320 if (edge_group == 0) { global_logic_i = (PetscReal)layer_index * layer_spacing_norm_i + epsilon; global_logic_k = variable_coord; }
321 else if (edge_group == 1) { global_logic_i = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_i) - epsilon; global_logic_k = variable_coord; }
322 else if (edge_group == 2) { global_logic_k = (PetscReal)layer_index * layer_spacing_norm_k + epsilon; global_logic_i = variable_coord; }
323 else /* edge_group == 3 */ { global_logic_k = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_k) - epsilon; global_logic_i = variable_coord; }
324 break;
325 case BC_FACE_POS_Y:
326 global_logic_j = 1.0 - (0.5 * layer_spacing_norm_j);
327 if (edge_group == 0) { global_logic_i = (PetscReal)layer_index * layer_spacing_norm_i + epsilon; global_logic_k = variable_coord; }
328 else if (edge_group == 1) { global_logic_i = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_i) - epsilon; global_logic_k = variable_coord; }
329 else if (edge_group == 2) { global_logic_k = (PetscReal)layer_index * layer_spacing_norm_k + epsilon; global_logic_i = variable_coord; }
330 else /* edge_group == 3 */ { global_logic_k = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_k) - epsilon; global_logic_i = variable_coord; }
331 break;
332 case BC_FACE_NEG_Z:
333 global_logic_k = 0.5 * layer_spacing_norm_k;
334 if (edge_group == 0) { global_logic_i = (PetscReal)layer_index * layer_spacing_norm_i + epsilon; global_logic_j = variable_coord; }
335 else if (edge_group == 1) { global_logic_i = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_i) - epsilon; global_logic_j = variable_coord; }
336 else if (edge_group == 2) { global_logic_j = (PetscReal)layer_index * layer_spacing_norm_j + epsilon; global_logic_i = variable_coord; }
337 else /* edge_group == 3 */ { global_logic_j = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_j) - epsilon; global_logic_i = variable_coord; }
338 break;
339 case BC_FACE_POS_Z:
340 global_logic_k = 1.0 - (0.5 * layer_spacing_norm_k);
341 if (edge_group == 0) { global_logic_i = (PetscReal)layer_index * layer_spacing_norm_i + epsilon; global_logic_j = variable_coord; }
342 else if (edge_group == 1) { global_logic_i = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_i) - epsilon; global_logic_j = variable_coord; }
343 else if (edge_group == 2) { global_logic_j = (PetscReal)layer_index * layer_spacing_norm_j + epsilon; global_logic_i = variable_coord; }
344 else /* edge_group == 3 */ { global_logic_j = 1.0 - ((PetscReal)layer_index * layer_spacing_norm_j) - epsilon; global_logic_i = variable_coord; }
345 break;
346 }
347
349 "[Rank %d] Particle %lld assigned to line %d (edge group %d, layer %d) with variable_coord=%.4f.\n"
350 " -> Global logical coords: (i,j,k) = (%.6f, %.6f, %.6f)\n",
351 rank_for_logging, (long long)particle_global_id, line_index, edge_group, layer_index, variable_coord,
352 global_logic_i, global_logic_j, global_logic_k);
353
354 // --- Step 4: Convert global logical coordinate to global cell index and intra-cell logicals ---
355 PetscReal global_cell_coord_i = global_logic_i * IM_cells_global;
356 PetscInt I_g = (PetscInt)global_cell_coord_i;
357 *xi_metric_logic_out = global_cell_coord_i - I_g;
358
359 PetscReal global_cell_coord_j = global_logic_j * JM_cells_global;
360 PetscInt J_g = (PetscInt)global_cell_coord_j;
361 *eta_metric_logic_out = global_cell_coord_j - J_g;
362
363 PetscReal global_cell_coord_k = global_logic_k * KM_cells_global;
364 PetscInt K_g = (PetscInt)global_cell_coord_k;
365 *zta_metric_logic_out = global_cell_coord_k - K_g;
366
367 // --- Step 5: Check if this rank owns the target cell and finalize outputs ---
368 if ((I_g >= info->xs && I_g < info->xs + info->xm) &&
369 (J_g >= info->ys && J_g < info->ys + info->ym) &&
370 (K_g >= info->zs && K_g < info->zs + info->zm))
371 {
372 // Convert global cell index to the local node index for this rank's DA patch
373 *ci_metric_lnode_out = (I_g - info->xs) + xs_gnode_rank;
374 *cj_metric_lnode_out = (J_g - info->ys) + ys_gnode_rank;
375 *ck_metric_lnode_out = (K_g - info->zs) + zs_gnode_rank;
376 *placement_successful_out = PETSC_TRUE;
377 }
378
380 "[Rank %d] Particle %lld placement %s.\n",
381 rank_for_logging, (long long)particle_global_id,
382 (*placement_successful_out ? "SUCCESSFUL" : "NOT ON THIS RANK"));
383
384 if(*placement_successful_out){
385 LOG_ALLOW(LOCAL,LOG_TRACE,"Local cell origin node: (I,J,K) = (%d,%d,%d), intra-cell logicals: (xi,eta,zta)=(%.6f,%.6f,%.6f)\n",
386 *ci_metric_lnode_out, *cj_metric_lnode_out, *ck_metric_lnode_out,
387 *xi_metric_logic_out, *eta_metric_logic_out, *zta_metric_logic_out);
388 }
389
390 PetscFunctionReturn(0);
391}
PetscInt np
Definition variables.h:990
The master context for the entire simulation.
Definition variables.h:859
Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetRandomCellAndLogicalCoordsOnInletFace()

PetscErrorCode GetRandomCellAndLogicalCoordsOnInletFace ( UserCtx *  user,
const DMDALocalInfo *  info,
PetscInt  xs_gnode_rank,
PetscInt  ys_gnode_rank,
PetscInt  zs_gnode_rank,
PetscInt  IM_nodes_global,
PetscInt  JM_nodes_global,
PetscInt  KM_nodes_global,
PetscRandom *  rand_logic_i_ptr,
PetscRandom *  rand_logic_j_ptr,
PetscRandom *  rand_logic_k_ptr,
PetscInt *  ci_metric_lnode_out,
PetscInt *  cj_metric_lnode_out,
PetscInt *  ck_metric_lnode_out,
PetscReal *  xi_metric_logic_out,
PetscReal *  eta_metric_logic_out,
PetscReal *  zta_metric_logic_out 
)

Assuming the current rank services the inlet face, this function selects a random cell (owned by this rank on that face) and random logical coordinates within that cell, suitable for placing a particle on the inlet surface.

It is the caller's responsibility to ensure CanRankServiceInletFace returned true.

Parameters
userPointer to UserCtx.
infoPointer to DMDALocalInfo for the current rank (node-based).
xs_gnode_rankLocal i-start node index (including ghosts) for this rank.
ys_gnode_rankLocal j-start node index (including ghosts) for this rank.
zs_gnode_rankLocal k-start node index (including ghosts) for this rank.
IM_nodes_globalGlobal node count in i.
JM_nodes_globalGlobal node count in j.
KM_nodes_globalGlobal node count in k.
rand_logic_i_ptrRNG handle for sampling local logical xi.
rand_logic_j_ptrRNG handle for sampling local logical eta.
rand_logic_k_ptrRNG handle for sampling local logical zta.
[out]ci_metric_lnode_outLocal i node index of selected cell origin.
[out]cj_metric_lnode_outLocal j node index of selected cell origin.
[out]ck_metric_lnode_outLocal k node index of selected cell origin.
[out]xi_metric_logic_outLogical xi coordinate in [0,1].
[out]eta_metric_logic_outLogical eta coordinate in [0,1].
[out]zta_metric_logic_outLogical zta coordinate in [0,1].
Returns
PetscErrorCode

Assuming the current rank services the inlet face, this function selects a random cell (owned by this rank on that face) and random logical coordinates within that cell, suitable for placing a particle on the inlet surface.

Local to this translation unit.

Definition at line 400 of file Boundaries.c.

407{
408 PetscErrorCode ierr = 0;
409 PetscReal r_val_i_sel, r_val_j_sel, r_val_k_sel;
410 PetscInt local_cell_idx_on_face_dim1 = 0; // 0-indexed relative to owned cells on face
411 PetscInt local_cell_idx_on_face_dim2 = 0;
412 PetscMPIInt rank_for_logging;
413
414 PetscFunctionBeginUser;
415
417
418 ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank_for_logging); CHKERRQ(ierr);
419
420 // Get number of cells this rank owns in each dimension (tangential to the face mainly)
421 PetscInt owned_start_cell_i, num_owned_cells_on_rank_i;
422 PetscInt owned_start_cell_j, num_owned_cells_on_rank_j;
423 PetscInt owned_start_cell_k, num_owned_cells_on_rank_k;
424
425 ierr = GetOwnedCellRange(info, 0, &owned_start_cell_i, &num_owned_cells_on_rank_i); CHKERRQ(ierr);
426 ierr = GetOwnedCellRange(info, 1, &owned_start_cell_j, &num_owned_cells_on_rank_j); CHKERRQ(ierr);
427 ierr = GetOwnedCellRange(info, 2, &owned_start_cell_k, &num_owned_cells_on_rank_k); CHKERRQ(ierr);
428
429 // Defaults for cell origin node (local index for the rank's DA patch, including ghosts)
430 *ci_metric_lnode_out = xs_gnode_rank; *cj_metric_lnode_out = ys_gnode_rank; *ck_metric_lnode_out = zs_gnode_rank;
431 // Defaults for logical coordinates
432 *xi_metric_logic_out = 0.5; *eta_metric_logic_out = 0.5; *zta_metric_logic_out = 0.5;
433
434 // Index of the last cell (0-indexed) in each global direction
435 PetscInt last_global_cell_idx_i = (IM_nodes_global > 1) ? (IM_nodes_global - 2) : -1;
436 PetscInt last_global_cell_idx_j = (JM_nodes_global > 1) ? (JM_nodes_global - 2) : -1;
437 PetscInt last_global_cell_idx_k = (KM_nodes_global > 1) ? (KM_nodes_global - 2) : -1;
438
439 LOG_ALLOW(LOCAL, LOG_INFO, "PARTICLE_INIT_DEBUG Rank %d: Inlet face %s.\n"
440 " Owned cells (i,j,k): (%d,%d,%d)\n"
441 " Global nodes (I,J,K): (%d,%d,%d)\n"
442 " info->xs,ys,zs (first owned node GLOBAL): (%d,%d,%d)\n"
443 " info->xm,ym,zm (num owned nodes GLOBAL): (%d,%d,%d)\n"
444 " xs_gnode_rank,ys_gnode_rank,zs_gnode_rank (DMDAGetCorners): (%d,%d,%d)\n"
445 " owned_start_cell (i,j,k) GLOBAL: (%d,%d,%d)\n"
446 " last_global_cell_idx (i,j,k): (%d,%d,%d)\n",
447 rank_for_logging, BCFaceToString((BCFace)user->identifiedInletBCFace),
448 num_owned_cells_on_rank_i,num_owned_cells_on_rank_j,num_owned_cells_on_rank_k,
449 IM_nodes_global,JM_nodes_global,KM_nodes_global,
450 info->xs, info->ys, info->zs,
451 info->xm, info->ym, info->zm,
452 xs_gnode_rank,ys_gnode_rank,zs_gnode_rank,
453 owned_start_cell_i, owned_start_cell_j, owned_start_cell_k,
454 last_global_cell_idx_i, last_global_cell_idx_j, last_global_cell_idx_k);
455
456
457 switch (user->identifiedInletBCFace) {
458 case BC_FACE_NEG_X: // Particle on -X face of cell C_0 (origin node N_0)
459 // Cell origin node is the first owned node in I by this rank (global index info->xs).
460 // Its local index within the rank's DA (incl ghosts) is xs_gnode_rank.
461 *ci_metric_lnode_out = xs_gnode_rank;
462 *xi_metric_logic_out = 1.0e-6;
463
464 // Tangential dimensions are J and K. Select an owned cell randomly on this face.
465 // num_owned_cells_on_rank_j/k must be > 0 (checked by CanRankServiceInletFace)
466 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, &r_val_j_sel); CHKERRQ(ierr);
467 local_cell_idx_on_face_dim1 = (PetscInt)(r_val_j_sel * num_owned_cells_on_rank_j); // Index among owned J-cells
468 local_cell_idx_on_face_dim1 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim1), num_owned_cells_on_rank_j - 1);
469 *cj_metric_lnode_out = ys_gnode_rank + local_cell_idx_on_face_dim1; // Offset from start of rank's J-nodes
470
471 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, &r_val_k_sel); CHKERRQ(ierr);
472 local_cell_idx_on_face_dim2 = (PetscInt)(r_val_k_sel * num_owned_cells_on_rank_k);
473 local_cell_idx_on_face_dim2 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim2), num_owned_cells_on_rank_k - 1);
474 *ck_metric_lnode_out = zs_gnode_rank + local_cell_idx_on_face_dim2;
475
476 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, eta_metric_logic_out); CHKERRQ(ierr);
477 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, zta_metric_logic_out); CHKERRQ(ierr);
478 break;
479
480 case BC_FACE_POS_X: // Particle on +X face of cell C_last_I (origin node N_last_I_origin)
481 // Origin node of the last I-cell is global_node_idx = last_global_cell_idx_i.
482 // Its local index in rank's DA: (last_global_cell_idx_i - info->xs) + xs_gnode_rank
483 *ci_metric_lnode_out = xs_gnode_rank + (last_global_cell_idx_i - info->xs);
484 *xi_metric_logic_out = 1.0 - 1.0e-6;
485
486 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, &r_val_j_sel); CHKERRQ(ierr);
487 local_cell_idx_on_face_dim1 = (PetscInt)(r_val_j_sel * num_owned_cells_on_rank_j);
488 local_cell_idx_on_face_dim1 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim1), num_owned_cells_on_rank_j - 1);
489 *cj_metric_lnode_out = ys_gnode_rank + local_cell_idx_on_face_dim1;
490
491 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, &r_val_k_sel); CHKERRQ(ierr);
492 local_cell_idx_on_face_dim2 = (PetscInt)(r_val_k_sel * num_owned_cells_on_rank_k);
493 local_cell_idx_on_face_dim2 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim2), num_owned_cells_on_rank_k - 1);
494 *ck_metric_lnode_out = zs_gnode_rank + local_cell_idx_on_face_dim2;
495
496 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, eta_metric_logic_out); CHKERRQ(ierr);
497 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, zta_metric_logic_out); CHKERRQ(ierr);
498 break;
499 // ... (Cases for Y and Z faces, following the same pattern) ...
500 case BC_FACE_NEG_Y:
501 *cj_metric_lnode_out = ys_gnode_rank;
502 *eta_metric_logic_out = 1.0e-6;
503 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, &r_val_i_sel); CHKERRQ(ierr);
504 local_cell_idx_on_face_dim1 = (PetscInt)(r_val_i_sel * num_owned_cells_on_rank_i);
505 local_cell_idx_on_face_dim1 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim1), num_owned_cells_on_rank_i - 1);
506 *ci_metric_lnode_out = xs_gnode_rank + local_cell_idx_on_face_dim1;
507 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, &r_val_k_sel); CHKERRQ(ierr);
508 local_cell_idx_on_face_dim2 = (PetscInt)(r_val_k_sel * num_owned_cells_on_rank_k);
509 local_cell_idx_on_face_dim2 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim2), num_owned_cells_on_rank_k - 1);
510 *ck_metric_lnode_out = zs_gnode_rank + local_cell_idx_on_face_dim2;
511 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, xi_metric_logic_out); CHKERRQ(ierr);
512 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, zta_metric_logic_out); CHKERRQ(ierr);
513 break;
514 case BC_FACE_POS_Y:
515 *cj_metric_lnode_out = ys_gnode_rank + (last_global_cell_idx_j - info->ys);
516 *eta_metric_logic_out = 1.0 - 1.0e-6;
517 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, &r_val_i_sel); CHKERRQ(ierr);
518 local_cell_idx_on_face_dim1 = (PetscInt)(r_val_i_sel * num_owned_cells_on_rank_i);
519 local_cell_idx_on_face_dim1 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim1), num_owned_cells_on_rank_i - 1);
520 *ci_metric_lnode_out = xs_gnode_rank + local_cell_idx_on_face_dim1;
521 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, &r_val_k_sel); CHKERRQ(ierr);
522 local_cell_idx_on_face_dim2 = (PetscInt)(r_val_k_sel * num_owned_cells_on_rank_k);
523 local_cell_idx_on_face_dim2 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim2), num_owned_cells_on_rank_k - 1);
524 *ck_metric_lnode_out = zs_gnode_rank + local_cell_idx_on_face_dim2;
525 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, xi_metric_logic_out); CHKERRQ(ierr);
526 ierr = PetscRandomGetValueReal(*rand_logic_k_ptr, zta_metric_logic_out); CHKERRQ(ierr);
527 break;
528 case BC_FACE_NEG_Z: // Your example case
529 *ck_metric_lnode_out = zs_gnode_rank; // Cell origin is the first owned node in K by this rank
530 *zta_metric_logic_out = 1.0e-6; // Place particle slightly inside this cell from its -Z face
531 // Tangential dimensions are I and J
532 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, &r_val_i_sel); CHKERRQ(ierr);
533 local_cell_idx_on_face_dim1 = (PetscInt)(r_val_i_sel * num_owned_cells_on_rank_i);
534 local_cell_idx_on_face_dim1 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim1), num_owned_cells_on_rank_i - 1);
535 *ci_metric_lnode_out = xs_gnode_rank + local_cell_idx_on_face_dim1;
536
537 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, &r_val_j_sel); CHKERRQ(ierr);
538 local_cell_idx_on_face_dim2 = (PetscInt)(r_val_j_sel * num_owned_cells_on_rank_j);
539 local_cell_idx_on_face_dim2 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim2), num_owned_cells_on_rank_j - 1);
540 *cj_metric_lnode_out = ys_gnode_rank + local_cell_idx_on_face_dim2;
541
542 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, xi_metric_logic_out); CHKERRQ(ierr); // Intra-cell logical for I
543 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, eta_metric_logic_out); CHKERRQ(ierr); // Intra-cell logical for J
544 break;
545 case BC_FACE_POS_Z:
546 *ck_metric_lnode_out = zs_gnode_rank + (last_global_cell_idx_k - info->zs);
547 *zta_metric_logic_out = 1.0 - 1.0e-6;
548 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, &r_val_i_sel); CHKERRQ(ierr);
549 local_cell_idx_on_face_dim1 = (PetscInt)(r_val_i_sel * num_owned_cells_on_rank_i);
550 local_cell_idx_on_face_dim1 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim1), num_owned_cells_on_rank_i - 1);
551 *ci_metric_lnode_out = xs_gnode_rank + local_cell_idx_on_face_dim1;
552 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, &r_val_j_sel); CHKERRQ(ierr);
553 local_cell_idx_on_face_dim2 = (PetscInt)(r_val_j_sel * num_owned_cells_on_rank_j);
554 local_cell_idx_on_face_dim2 = PetscMin(PetscMax(0, local_cell_idx_on_face_dim2), num_owned_cells_on_rank_j - 1);
555 *cj_metric_lnode_out = ys_gnode_rank + local_cell_idx_on_face_dim2;
556 ierr = PetscRandomGetValueReal(*rand_logic_i_ptr, xi_metric_logic_out); CHKERRQ(ierr);
557 ierr = PetscRandomGetValueReal(*rand_logic_j_ptr, eta_metric_logic_out); CHKERRQ(ierr);
558 break;
559 default:
560 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "GetRandomCellAndLogicOnInletFace: Invalid user->identifiedInletBCFace %d. \n", user->identifiedInletBCFace);
561 }
562
563 PetscReal eps = 1.0e-7;
565 *eta_metric_logic_out = PetscMin(PetscMax(0.0, *eta_metric_logic_out), 1.0 - eps);
566 *zta_metric_logic_out = PetscMin(PetscMax(0.0, *zta_metric_logic_out), 1.0 - eps);
568 *xi_metric_logic_out = PetscMin(PetscMax(0.0, *xi_metric_logic_out), 1.0 - eps);
569 *zta_metric_logic_out = PetscMin(PetscMax(0.0, *zta_metric_logic_out), 1.0 - eps);
570 } else {
571 *xi_metric_logic_out = PetscMin(PetscMax(0.0, *xi_metric_logic_out), 1.0 - eps);
572 *eta_metric_logic_out = PetscMin(PetscMax(0.0, *eta_metric_logic_out), 1.0 - eps);
573 }
574
575 LOG_ALLOW(LOCAL, LOG_VERBOSE, "Rank %d: Target Cell Node =(%d,%d,%d). (xi,et,zt)=(%.2e,%.2f,%.2f). \n",
576 rank_for_logging, *ci_metric_lnode_out, *cj_metric_lnode_out, *ck_metric_lnode_out,
577 *xi_metric_logic_out, *eta_metric_logic_out, *zta_metric_logic_out);
578
580
581 PetscFunctionReturn(0);
582}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ClassifyMomentumRow()

MomentumRowType ClassifyMomentumRow ( UserCtx *  user,
PetscInt  i,
PetscInt  j,
PetscInt  k,
PetscInt  component,
PetscInt *  ri,
PetscInt *  rj,
PetscInt *  rk 
)

Single source of truth for "which staggered momentum rows are unknowns".

Every consumer of the momentum system must agree on which rows the solver is responsible for, and every consumer must derive that answer from this function rather than restating the index arithmetic locally. Three independent restatements previously disagreed, and the disagreement was silent: the residual assembly skipped the periodic duplicate column at index 0 while nothing zeroed it, so ComputeTotalResidual()'s BDF term accumulated there without bound and the reported residual norm stopped describing the state.

The classification depends only on user->info, the configured boundary types, and the queried index; it reads no field data and performs no communication, so it is safe to call inside assembly loops.

Periodicity of an axis is taken from that axis's NEGATIVE face, matching ComputeRHS() and TransferPeriodicStaggeredFieldByDirection(). A periodic axis is expected to carry PERIODIC on both of its faces.

Callers act on the classification differently, and both actions are correct:

  • residual/pseudo-time consumers (EnforceRHSBoundaryConditions()) zero every non-physical row, because the value there is imposed immediately afterwards by the boundary sweep or the periodic synchronisation;
  • the matrix-free Newton path substitutes an explicit equation instead (F = X - U_conditioned, F = X, F = X_dup - X_rep), because a zeroed row would leave a zero Jacobian row.
Parameters
[in]userBlock context supplying info and boundary_faces.
[in]iLocation index along xi.
[in]jLocation index along eta.
[in]kLocation index along zeta.
[in]componentStaggered component of the row (0 = xi, 1 = eta, 2 = zeta).
[out]riRepresentative xi index; equals i unless the row wraps.
[out]rjRepresentative eta index; equals j unless the row wraps.
[out]rkRepresentative zeta index; equals k unless the row wraps.
Returns
The row classification. Only MOM_ROW_PHYSICAL denotes an unknown.

Single source of truth for "which staggered momentum rows are unknowns".

Pure index/boundary-type arithmetic: no field reads, no communication. Precedence matters. A conditioned row is reported first because its explicit Dirichlet value is more specific than the homogeneous fallback, and a periodic duplicate is reported before the homogeneous case because the Newton path needs its representative index to build F = X_dup - X_rep.

Definition at line 597 of file Boundaries.c.

599{
600 const PetscInt mx = user->info.mx, my = user->info.my, mz = user->info.mz;
601 const PetscInt coord[3] = {i, j, k};
602 const PetscInt size[3] = {mx, my, mz};
603 const BCFace neg_face[3] = {BC_FACE_NEG_X, BC_FACE_NEG_Y, BC_FACE_NEG_Z};
604 PetscBool periodic[3], periodic_duplicate = PETSC_FALSE;
605 PetscBool residual_zeroed = PETSC_FALSE, conditioned = PETSC_FALSE;
606
607 *ri = i; *rj = j; *rk = k;
608 for (PetscInt axis = 0; axis < 3; ++axis) {
609 periodic[axis] = (PetscBool)(
610 user->boundary_faces[neg_face[axis]].mathematical_type == PERIODIC);
611 if (periodic[axis] && coord[axis] == 0) {
612 periodic_duplicate = PETSC_TRUE;
613 if (axis == 0) *ri = -2;
614 else if (axis == 1) *rj = -2;
615 else *rk = -2;
616 }
617 if (periodic[axis] && coord[axis] == size[axis] - 1) {
618 periodic_duplicate = PETSC_TRUE;
619 if (axis == 0) *ri = mx + 1;
620 else if (axis == 1) *rj = my + 1;
621 else *rk = mz + 1;
622 }
623
624 if (!periodic[axis] && coord[axis] == 0) residual_zeroed = PETSC_TRUE;
625 if (coord[axis] == size[axis] - 1) residual_zeroed = PETSC_TRUE;
626 if (!periodic[axis] && coord[axis] == size[axis] - 2 && component == axis)
627 residual_zeroed = PETSC_TRUE;
628 }
629
630 if (!periodic[component] &&
631 (coord[component] == 0 || coord[component] == size[component] - 2)) {
632 PetscBool tangential_interior = PETSC_TRUE;
633 for (PetscInt axis = 0; axis < 3; ++axis) {
634 if (axis == component) continue;
635 if (coord[axis] < 1 || coord[axis] > size[axis] - 2)
636 tangential_interior = PETSC_FALSE;
637 }
638 conditioned = tangential_interior;
639 }
640
641 if (conditioned) return MOM_ROW_FIXED_CONDITIONED;
642 if (periodic_duplicate) return MOM_ROW_PERIODIC_DUPLICATE;
643 if (residual_zeroed) return MOM_ROW_FIXED_HOMOGENEOUS;
644 return MOM_ROW_PHYSICAL;
645}
DMDALocalInfo info
Definition variables.h:1086
Here is the caller graph for this function:

◆ MomentumRowIsSolidMasked()

PetscBool MomentumRowIsSolidMasked ( const PetscReal ***  nvert,
PetscInt  i,
PetscInt  j,
PetscInt  k,
PetscInt  component 
)

Reports whether a momentum row is masked out by the solid-cell field.

ComputeRHS() zeroes the residual at a solid cell and at the staggered rows whose downstream neighbour is solid, so those rows carry no equation. A solver that only marches on the residual needs nothing more: a zero residual means no update. A solver that assembles a matrix and solves F(X) = 0 does, because such a row has a zero Jacobian row and a zero column, leaving the unknown undetermined. Those rows must be constrained like any other row that carries no unknown.

The condition mirrors the residual's own masking exactly. If one changes, so must the other, or the assembled operator stops matching the residual it preconditions.

Parameters
[in]nvertGhosted solid-cell field, or NULL to apply no masking.
[in]iLocation index along xi.
[in]jLocation index along eta.
[in]kLocation index along zeta.
[in]componentStaggered component of the row (0 = xi, 1 = eta, 2 = zeta).
Returns
PETSC_TRUE when the row is masked and therefore carries no unknown.

Reports whether a momentum row is masked out by the solid-cell field.

Full API contract is documented with the header declaration in include/Boundaries.h.

See also
MomentumRowIsSolidMasked()

Definition at line 655 of file Boundaries.c.

657{
658 /* PICURV_SOLID_THRESHOLD mirrors the 0.1 fluid test ComputeRHS() applies. */
659 const PetscReal threshold = 0.1;
660
661 if (nvert == NULL) return PETSC_FALSE;
662 /* A solid cell carries no momentum equation in any component. */
663 if (nvert[k][j][i] > threshold) return PETSC_TRUE;
664 /* A staggered row also loses its equation when the cell it points into is solid. */
665 switch (component) {
666 case 0: return (PetscBool)(nvert[k][j][i + 1] > threshold);
667 case 1: return (PetscBool)(nvert[k][j + 1][i] > threshold);
668 case 2: return (PetscBool)(nvert[k + 1][j][i] > threshold);
669 default: return PETSC_FALSE;
670 }
671}
Here is the caller graph for this function:

◆ EnforceRHSBoundaryConditions()

PetscErrorCode EnforceRHSBoundaryConditions ( UserCtx *  user)

Zeroes every momentum RHS row that does not carry an independent unknown.

The set of such rows is not restated here: each owned location and component is asked of ClassifyMomentumRow(), and anything other than MOM_ROW_PHYSICAL is zeroed. That covers, without enumerating them,

  • strong Dirichlet rows on non-periodic faces, so the time-stepping scheme cannot alter the values ApplyBoundaryConditions() has just set;
  • dummy layers at the far index of every axis, which hold no unknown; and
  • periodic duplicate columns, whose value the next SynchronizePeriodicStaggeredFields() copies from the wrapped master.

The last case is the one that must not be skipped. ComputeRHS() leaves the transverse components of a periodic duplicate column untouched, so a row left unzeroed here retains its previous contents while ComputeTotalResidual() adds the BDF term on top of them on every call. The residual norm then grows by |dU|/dt per evaluation regardless of the state, and no pseudo-time iteration can reduce it.

Call immediately after the RHS vector is fully assembled (spatial + temporal terms) and before it is used in a time-stepping update.

Parameters
userThe UserCtx for the specific block being computed.
Returns
PetscErrorCode 0 on success.

Zeroes every momentum RHS row that does not carry an independent unknown.

The sweep is deliberately expressed over every owned location rather than over the six boundary slabs: restating "which indices can be non-physical" here is exactly the duplication that let the periodic duplicate column go unzeroed. ClassifyMomentumRow() is a handful of integer comparisons and the walk is a single pass with no stencil access, which is negligible next to the several ghosted stencil passes ComputeRHS() has already made over the same range. MomentumNewtonKrylov_ApplyConstraints() walks the same range the same way.

Definition at line 686 of file Boundaries.c.

687{
688 PetscErrorCode ierr;
689 DMDALocalInfo info = user->info;
690 Cmpnts ***rhs;
691
692 PetscFunctionBeginUser;
694
695 // Get a writable pointer to the local data of the global RHS vector.
696 ierr = DMDAVecGetArray(user->fda, user->Rhs, &rhs); CHKERRQ(ierr);
697
698 for (PetscInt k = info.zs; k < info.zs + info.zm; k++) {
699 for (PetscInt j = info.ys; j < info.ys + info.ym; j++) {
700 for (PetscInt i = info.xs; i < info.xs + info.xm; i++) {
701 PetscScalar *row = &rhs[k][j][i].x; /* .x/.y/.z are contiguous */
702 for (PetscInt component = 0; component < 3; component++) {
703 PetscInt ri, rj, rk;
704 if (ClassifyMomentumRow(user, i, j, k, component, &ri, &rj, &rk) != MOM_ROW_PHYSICAL)
705 row[component] = 0.0;
706 }
707 }
708 }
709 }
710
711 // --- Release the pointer to the local data ---
712 ierr = DMDAVecRestoreArray(user->fda, user->Rhs, &rhs); CHKERRQ(ierr);
713
714 LOG_ALLOW(LOCAL, LOG_TRACE, "Rank %d, Block %d: Finished enforcing RHS boundary conditions.\n",
715 user->simCtx->rank, user->_this);
716
718
719 PetscFunctionReturn(0);
720}
MomentumRowType ClassifyMomentumRow(UserCtx *user, PetscInt i, PetscInt j, PetscInt k, PetscInt component, PetscInt *ri, PetscInt *rj, PetscInt *rk)
Implementation of ClassifyMomentumRow().
Definition Boundaries.c:597
PetscInt _this
Definition variables.h:1092
PetscScalar x
Definition variables.h:122
A 3D point or vector with PetscScalar components.
Definition variables.h:121
Here is the call graph for this function:
Here is the caller graph for this function:

◆ SynchronizePeriodicCellFields()

PetscErrorCode SynchronizePeriodicCellFields ( UserCtx *  user,
PetscInt  num_fields,
const FieldId  field_ids[] 
)

Synchronizes periodic endpoint cells for a list of cell-centered fields.

The fields are first communicated from global to local storage. Each periodic direction is then transferred in i-j-k order, with an intermediate ghost refresh after every active direction so periodic edges and corners inherit the values established by earlier directions. Only global duplicate planes in active periodic directions are repaired; non-periodic directions are untouched. The routine is a no-op, including no local refresh, when every direction is nonperiodic. During active periodic synchronization it internally refreshes the local vectors, but it is not a general replacement for UpdateLocalGhosts().

Supported fields are selected by FIELD_CAPABILITY_PERIODIC_CELL_SYNC in the field catalog.

Parameters
userThe main UserCtx struct.
num_fieldsThe number of entries in field_ids.
field_idsThe cell-centered fields to synchronize.
Returns
PetscErrorCode 0 on success.

Synchronizes periodic endpoint cells for a list of cell-centered fields.

Full API contract is documented with the header declaration in include/Boundaries.h.

Definition at line 1647 of file Boundaries.c.

1648{
1649 PetscErrorCode ierr;
1650 PetscBool periodic_i;
1651 PetscBool periodic_j;
1652 PetscBool periodic_k;
1653
1654 PetscFunctionBeginUser;
1655
1656 PetscCheck(num_fields >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
1657 "Number of cell fields cannot be negative.");
1658 if (num_fields == 0) PetscFunctionReturn(0);
1659 PetscCheck(field_ids != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
1660 "Cell field-ID array cannot be NULL.");
1661
1662 periodic_i =
1665 periodic_j =
1668 periodic_k =
1671
1672 if (!periodic_i && !periodic_j && !periodic_k) PetscFunctionReturn(0);
1673
1674 for (PetscInt field = 0; field < num_fields; field++) {
1675 ierr = UpdateLocalGhosts(user, field_ids[field]); CHKERRQ(ierr);
1676 }
1677
1678 if (periodic_i) {
1679 for (PetscInt field = 0; field < num_fields; field++) {
1680 ierr = TransferPeriodicFieldByDirection(user, field_ids[field], 'i'); CHKERRQ(ierr);
1681 }
1682 for (PetscInt field = 0; field < num_fields; field++) {
1683 ierr = UpdateLocalGhosts(user, field_ids[field]); CHKERRQ(ierr);
1684 }
1685 }
1686
1687 if (periodic_j) {
1688 for (PetscInt field = 0; field < num_fields; field++) {
1689 ierr = TransferPeriodicFieldByDirection(user, field_ids[field], 'j'); CHKERRQ(ierr);
1690 }
1691 for (PetscInt field = 0; field < num_fields; field++) {
1692 ierr = UpdateLocalGhosts(user, field_ids[field]); CHKERRQ(ierr);
1693 }
1694 }
1695
1696 if (periodic_k) {
1697 for (PetscInt field = 0; field < num_fields; field++) {
1698 ierr = TransferPeriodicFieldByDirection(user, field_ids[field], 'k'); CHKERRQ(ierr);
1699 }
1700 for (PetscInt field = 0; field < num_fields; field++) {
1701 ierr = UpdateLocalGhosts(user, field_ids[field]); CHKERRQ(ierr);
1702 }
1703 }
1704
1705 PetscFunctionReturn(0);
1706}
static PetscErrorCode TransferPeriodicFieldByDirection(UserCtx *user, FieldId field_id, char direction)
Copies one cell field's wrapped local values onto the owned periodic duplicate plane.
PetscErrorCode UpdateLocalGhosts(UserCtx *user, FieldId field_id)
Updates the local vector (including ghost points) from its corresponding global vector.
Definition setup.c:2489
Here is the call graph for this function:
Here is the caller graph for this function:

◆ SynchronizePeriodicFaceFields()

PetscErrorCode SynchronizePeriodicFaceFields ( UserCtx *  user,
char  face_direction,
PetscInt  num_fields,
const FieldId  field_ids[] 
)

Synchronizes persistent fields belonging to one face family.

The function performs deterministic I/J/K directional passes with an intermediate ghost refresh after each active periodic direction. It updates persistent global seam/dummy values only; face-specific local stencil repair remains a separate operation.

Parameters
userThe main UserCtx struct.
face_directionFace family shared by every field (‘'i’,'j', or'k'`).
[in]num_fieldsCount of registered face fields.
field_idsRegistered persistent face-field identities.
Returns
PetscErrorCode 0 on success.

Definition at line 1881 of file Boundaries.c.

1883{
1884 PetscErrorCode ierr;
1885 const char periodic_directions[3] = {'i', 'j', 'k'};
1886 const BCFace negative_faces[3] = {BC_FACE_NEG_X, BC_FACE_NEG_Y, BC_FACE_NEG_Z};
1887 const BCFace positive_faces[3] = {BC_FACE_POS_X, BC_FACE_POS_Y, BC_FACE_POS_Z};
1888
1889 PetscFunctionBeginUser;
1890 PetscCheck(num_fields >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
1891 "Number of face fields cannot be negative.");
1892 if (num_fields == 0) PetscFunctionReturn(0);
1893 PetscCheck(field_ids != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
1894 "Face field-ID array cannot be NULL.");
1895
1896 for (PetscInt field = 0; field < num_fields; field++) {
1897 DM dm;
1898 Vec global_vec, local_vec;
1899 PetscInt dof;
1900 PetscBool is_coordinate;
1901 PetscCall(GetPersistentFaceField(user, field_ids[field], face_direction,
1902 &dm, &global_vec, &local_vec, &dof));
1903 ierr = UpdateLocalGhosts(user, field_ids[field]); CHKERRQ(ierr);
1904 ierr = IsFaceCenterCoordinateField(field_ids[field], &is_coordinate); CHKERRQ(ierr);
1905 if (is_coordinate) {
1906 ierr = TranslatePeriodicFaceCenterGhosts(user, local_vec); CHKERRQ(ierr);
1907 }
1908 }
1909
1910 for (PetscInt direction = 0; direction < 3; direction++) {
1911 const PetscBool active =
1912 user->boundary_faces[negative_faces[direction]].mathematical_type == PERIODIC ||
1913 user->boundary_faces[positive_faces[direction]].mathematical_type == PERIODIC;
1914 if (!active) continue;
1915
1916 for (PetscInt field = 0; field < num_fields; field++) {
1917 ierr = TransferPeriodicFaceFieldByDirection(user, field_ids[field], face_direction,
1918 periodic_directions[direction]); CHKERRQ(ierr);
1919 }
1920 for (PetscInt field = 0; field < num_fields; field++) {
1921 DM dm;
1922 Vec global_vec, local_vec;
1923 PetscInt dof;
1924 PetscBool is_coordinate;
1925 ierr = UpdateLocalGhosts(user, field_ids[field]); CHKERRQ(ierr);
1926 ierr = IsFaceCenterCoordinateField(field_ids[field], &is_coordinate); CHKERRQ(ierr);
1927 if (is_coordinate) {
1928 PetscCall(GetPersistentFaceField(user, field_ids[field], face_direction,
1929 &dm, &global_vec, &local_vec, &dof));
1930 ierr = TranslatePeriodicFaceCenterGhosts(user, local_vec); CHKERRQ(ierr);
1931 }
1932 }
1933 }
1934
1935 PetscFunctionReturn(0);
1936}
static PetscErrorCode GetPersistentFaceField(UserCtx *user, FieldId field_id, char face_direction, DM *dm, Vec *global_vec, Vec *local_vec, PetscInt *dof)
Resolves one registered persistent single-face-family field.
static PetscErrorCode TranslatePeriodicFaceCenterGhosts(UserCtx *user, Vec local_vec)
Applies geometric translations to wrapped face-center ghost coordinates.
static PetscErrorCode IsFaceCenterCoordinateField(FieldId field_id, PetscBool *is_coordinate)
Returns whether a registered face field stores physical coordinates.
static PetscErrorCode TransferPeriodicFaceFieldByDirection(UserCtx *user, FieldId field_id, char face_direction, char periodic_direction)
Transfers one registered face-family field along one periodic axis.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ SynchronizePeriodicStaggeredFields()

PetscErrorCode SynchronizePeriodicStaggeredFields ( UserCtx *  user,
PetscInt  num_fields,
const FieldId  field_ids[] 
)

Synchronizes persistent component-staggered vector fields.

The function performs deterministic I/J/K endpoint transfers with an intermediate ghost refresh after every active periodic direction. Currently Ucont is the only registered component-staggered field.

Parameters
userThe main UserCtx struct.
num_fieldsNumber of entries in field_ids.
field_idsRegistered component-staggered field identities.
Returns
PetscErrorCode 0 on success.

Synchronizes persistent component-staggered vector fields.

Definition at line 2015 of file Boundaries.c.

2017{
2018 PetscErrorCode ierr;
2019 const char periodic_directions[3] = {'i', 'j', 'k'};
2020 const BCFace negative_faces[3] = {BC_FACE_NEG_X, BC_FACE_NEG_Y, BC_FACE_NEG_Z};
2021 const BCFace positive_faces[3] = {BC_FACE_POS_X, BC_FACE_POS_Y, BC_FACE_POS_Z};
2022
2023 PetscFunctionBeginUser;
2024 PetscCheck(num_fields >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE,
2025 "Number of staggered fields cannot be negative.");
2026 if (num_fields == 0) PetscFunctionReturn(0);
2027 PetscCheck(field_ids != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2028 "Staggered field-ID array cannot be NULL.");
2029
2030 for (PetscInt field = 0; field < num_fields; field++) {
2031 DM dm;
2032 Vec global_vec, local_vec;
2033 PetscCall(GetPersistentStaggeredField(user, field_ids[field], &dm, &global_vec, &local_vec));
2034 ierr = UpdateLocalGhosts(user, field_ids[field]); CHKERRQ(ierr);
2035 }
2036
2037 for (PetscInt direction = 0; direction < 3; direction++) {
2038 const PetscBool active =
2039 user->boundary_faces[negative_faces[direction]].mathematical_type == PERIODIC ||
2040 user->boundary_faces[positive_faces[direction]].mathematical_type == PERIODIC;
2041 if (!active) continue;
2042
2043 for (PetscInt field = 0; field < num_fields; field++) {
2044 ierr = TransferPeriodicStaggeredFieldByDirection(user, field_ids[field],
2045 periodic_directions[direction]); CHKERRQ(ierr);
2046 }
2047 for (PetscInt field = 0; field < num_fields; field++) {
2048 ierr = UpdateLocalGhosts(user, field_ids[field]); CHKERRQ(ierr);
2049 }
2050 }
2051
2052 PetscFunctionReturn(0);
2053}
static PetscErrorCode GetPersistentStaggeredField(UserCtx *user, FieldId field_id, DM *dm, Vec *global_vec, Vec *local_vec)
Resolves one registered persistent component-staggered field.
static PetscErrorCode TransferPeriodicStaggeredFieldByDirection(UserCtx *user, FieldId field_id, char periodic_direction)
Transfers one component-staggered field along one periodic axis.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PreparePeriodicQuickStencilFields()

PetscErrorCode PreparePeriodicQuickStencilFields ( UserCtx *  user,
Vec  local_vector_field,
Vec  local_scalar_field 
)

Repairs the outer adjacent periodic ghosts used by QUICK cell stencils.

The supplied local vectors must already contain a current PETSc periodic ghost exchange. The vector and scalar fields are repaired two logical cells across each active periodic seam so QUICK's i-1/i+2 equivalents are valid.

Parameters
userMain block context containing periodic boundary metadata.
local_vector_fieldGhosted three-component cell-centered field.
local_scalar_fieldGhosted scalar cell-centered field.
Returns
PetscErrorCode 0 on success.

Repairs the outer adjacent periodic ghosts used by QUICK cell stencils.

Definition at line 2060 of file Boundaries.c.

2062{
2063 DMDALocalInfo info = user->info;
2064 Cmpnts ***vector_array;
2065 PetscReal ***scalar_array;
2066 const PetscInt xs = info.xs, xe = info.xs + info.xm;
2067 const PetscInt ys = info.ys, ye = info.ys + info.ym;
2068 const PetscInt zs = info.zs, ze = info.zs + info.zm;
2069 const PetscInt gxs = info.gxs, gxe = info.gxs + info.gxm;
2070 const PetscInt gys = info.gys, gye = info.gys + info.gym;
2071 const PetscInt gzs = info.gzs, gze = info.gzs + info.gzm;
2072
2073 PetscFunctionBeginUser;
2074 PetscCheck(local_vector_field && local_scalar_field, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2075 "QUICK stencil repair requires both local vector and scalar fields.");
2076 PetscCall(DMDAVecGetArray(user->fda, local_vector_field, &vector_array));
2077 PetscCall(DMDAVecGetArray(user->da, local_scalar_field, &scalar_array));
2078
2079 if (user->boundary_faces[BC_FACE_NEG_X].mathematical_type == PERIODIC && xs == 0) {
2080 for (PetscInt k = gzs; k < gze; k++) for (PetscInt j = gys; j < gye; j++) {
2081 vector_array[k][j][-1] = vector_array[k][j][-3];
2082 scalar_array[k][j][-1] = scalar_array[k][j][-3];
2083 }
2084 }
2085 if (user->boundary_faces[BC_FACE_POS_X].mathematical_type == PERIODIC && xe == info.mx) {
2086 for (PetscInt k = gzs; k < gze; k++) for (PetscInt j = gys; j < gye; j++) {
2087 vector_array[k][j][info.mx] = vector_array[k][j][info.mx + 2];
2088 scalar_array[k][j][info.mx] = scalar_array[k][j][info.mx + 2];
2089 }
2090 }
2091 if (user->boundary_faces[BC_FACE_NEG_Y].mathematical_type == PERIODIC && ys == 0) {
2092 for (PetscInt k = gzs; k < gze; k++) for (PetscInt i = gxs; i < gxe; i++) {
2093 vector_array[k][-1][i] = vector_array[k][-3][i];
2094 scalar_array[k][-1][i] = scalar_array[k][-3][i];
2095 }
2096 }
2097 if (user->boundary_faces[BC_FACE_POS_Y].mathematical_type == PERIODIC && ye == info.my) {
2098 for (PetscInt k = gzs; k < gze; k++) for (PetscInt i = gxs; i < gxe; i++) {
2099 vector_array[k][info.my][i] = vector_array[k][info.my + 2][i];
2100 scalar_array[k][info.my][i] = scalar_array[k][info.my + 2][i];
2101 }
2102 }
2103 if (user->boundary_faces[BC_FACE_NEG_Z].mathematical_type == PERIODIC && zs == 0) {
2104 for (PetscInt j = gys; j < gye; j++) for (PetscInt i = gxs; i < gxe; i++) {
2105 vector_array[-1][j][i] = vector_array[-3][j][i];
2106 scalar_array[-1][j][i] = scalar_array[-3][j][i];
2107 }
2108 }
2109 if (user->boundary_faces[BC_FACE_POS_Z].mathematical_type == PERIODIC && ze == info.mz) {
2110 for (PetscInt j = gys; j < gye; j++) for (PetscInt i = gxs; i < gxe; i++) {
2111 vector_array[info.mz][j][i] = vector_array[info.mz + 2][j][i];
2112 scalar_array[info.mz][j][i] = scalar_array[info.mz + 2][j][i];
2113 }
2114 }
2115
2116 PetscCall(DMDAVecRestoreArray(user->da, local_scalar_field, &scalar_array));
2117 PetscCall(DMDAVecRestoreArray(user->fda, local_vector_field, &vector_array));
2118 PetscFunctionReturn(0);
2119}
Here is the caller graph for this function:

◆ SynchronizePeriodicLocalStaggeredField()

PetscErrorCode SynchronizePeriodicLocalStaggeredField ( UserCtx *  user,
Vec  local_field 
)

Synchronizes one local-only component-staggered periodic work field.

This helper communicates locally computed owned entries, establishes the normal-component periodic endpoint values, and communicates once more.

Parameters
userMain block context containing periodic boundary metadata.
local_fieldGhosted local component-staggered vector.
Returns
PetscErrorCode 0 on success.

Synchronizes one local-only component-staggered periodic work field.

Definition at line 2126 of file Boundaries.c.

2127{
2128 DMDALocalInfo info = user->info;
2129 Cmpnts ***array;
2130 const PetscInt xs = info.xs, xe = info.xs + info.xm;
2131 const PetscInt ys = info.ys, ye = info.ys + info.ym;
2132 const PetscInt zs = info.zs, ze = info.zs + info.zm;
2133
2134 PetscFunctionBeginUser;
2135 PetscCheck(local_field, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
2136 "Local staggered field cannot be NULL.");
2137 PetscCall(DMLocalToLocalBegin(user->fda, local_field, INSERT_VALUES, local_field));
2138 PetscCall(DMLocalToLocalEnd(user->fda, local_field, INSERT_VALUES, local_field));
2139 PetscCall(DMDAVecGetArray(user->fda, local_field, &array));
2140
2141 if (user->boundary_faces[BC_FACE_NEG_X].mathematical_type == PERIODIC && xs == 0)
2142 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) array[k][j][0].x = array[k][j][-2].x;
2143 if (user->boundary_faces[BC_FACE_POS_X].mathematical_type == PERIODIC && xe == info.mx)
2144 for (PetscInt k = zs; k < ze; k++) for (PetscInt j = ys; j < ye; j++) array[k][j][info.mx - 1].x = array[k][j][info.mx + 1].x;
2145 if (user->boundary_faces[BC_FACE_NEG_Y].mathematical_type == PERIODIC && ys == 0)
2146 for (PetscInt k = zs; k < ze; k++) for (PetscInt i = xs; i < xe; i++) array[k][0][i].y = array[k][-2][i].y;
2147 if (user->boundary_faces[BC_FACE_POS_Y].mathematical_type == PERIODIC && ye == info.my)
2148 for (PetscInt k = zs; k < ze; k++) for (PetscInt i = xs; i < xe; i++) array[k][info.my - 1][i].y = array[k][info.my + 1][i].y;
2149 if (user->boundary_faces[BC_FACE_NEG_Z].mathematical_type == PERIODIC && zs == 0)
2150 for (PetscInt j = ys; j < ye; j++) for (PetscInt i = xs; i < xe; i++) array[0][j][i].z = array[-2][j][i].z;
2151 if (user->boundary_faces[BC_FACE_POS_Z].mathematical_type == PERIODIC && ze == info.mz)
2152 for (PetscInt j = ys; j < ye; j++) for (PetscInt i = xs; i < xe; i++) array[info.mz - 1][j][i].z = array[info.mz + 1][j][i].z;
2153
2154 PetscCall(DMDAVecRestoreArray(user->fda, local_field, &array));
2155 PetscCall(DMLocalToLocalBegin(user->fda, local_field, INSERT_VALUES, local_field));
2156 PetscCall(DMLocalToLocalEnd(user->fda, local_field, INSERT_VALUES, local_field));
2157 PetscFunctionReturn(0);
2158}
Here is the caller graph for this function:

◆ ApplyMetricsPeriodicBCs()

PetscErrorCode ApplyMetricsPeriodicBCs ( UserCtx *  user)

(Orchestrator) Updates all metric-related fields in the local ghost cell regions for periodic boundaries.

This function synchronizes cell-centered Aj and the persistent I/J/K metric face families through the canonical MPI-safe synchronizers.

Parameters
userThe main UserCtx struct.
Returns
PetscErrorCode 0 on success.

(Orchestrator) Updates all metric-related fields in the local ghost cell regions for periodic boundaries.

Local to this translation unit.

Definition at line 2166 of file Boundaries.c.

2167{
2168 PetscErrorCode ierr;
2169 PetscFunctionBeginUser;
2171
2172 const FieldId cell_fields[] = {FIELD_ID_AJ};
2173 const FieldId i_face_fields[] = {FIELD_ID_CENTX, FIELD_ID_CSI, FIELD_ID_ICSI,
2175 const FieldId j_face_fields[] = {FIELD_ID_CENTY, FIELD_ID_ETA, FIELD_ID_JCSI,
2177 const FieldId k_face_fields[] = {FIELD_ID_CENTZ, FIELD_ID_ZET, FIELD_ID_KCSI,
2179
2180 ierr = SynchronizePeriodicCellFields(user, 1, cell_fields); CHKERRQ(ierr);
2181 ierr = SynchronizePeriodicFaceFields(user, 'i', 6, i_face_fields); CHKERRQ(ierr);
2182 ierr = SynchronizePeriodicFaceFields(user, 'j', 6, j_face_fields); CHKERRQ(ierr);
2183 ierr = SynchronizePeriodicFaceFields(user, 'k', 6, k_face_fields); CHKERRQ(ierr);
2184
2186 PetscFunctionReturn(0);
2187}
PetscErrorCode SynchronizePeriodicFaceFields(UserCtx *user, char face_direction, PetscInt num_fields, const FieldId field_ids[])
Synchronizes persistent fields belonging to one face family.
PetscErrorCode SynchronizePeriodicCellFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Implementation of SynchronizePeriodicCellFields().
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_JETA
@ FIELD_ID_CENTZ
@ FIELD_ID_CSI
@ FIELD_ID_IAJ
@ FIELD_ID_KETA
@ FIELD_ID_JAJ
@ FIELD_ID_KAJ
@ FIELD_ID_AJ
@ FIELD_ID_CENTY
@ FIELD_ID_KZET
@ FIELD_ID_IETA
@ FIELD_ID_ICSI
@ FIELD_ID_ETA
@ FIELD_ID_JCSI
@ FIELD_ID_JZET
@ FIELD_ID_IZET
@ FIELD_ID_ZET
@ FIELD_ID_KCSI
@ FIELD_ID_CENTX
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ApplyPeriodicBCs()

PetscErrorCode ApplyPeriodicBCs ( UserCtx *  user)

Applies periodic boundary conditions by copying data across domain boundaries for all relevant fields.

This is the canonical periodic orchestrator for geometric consistency. It updates Ucat, P, and Nvert through the generic cell synchronizer and updates staggered Ucont through the component-staggered synchronizer.

Future extension rule: add new periodic variables by extending the existing field string dispatchers and invoking them from this orchestrator.

Parameters
userThe main UserCtx struct.
Returns
PetscErrorCode 0 on success.

Applies periodic boundary conditions by copying data across domain boundaries for all relevant fields.

Local to this translation unit.

Definition at line 2195 of file Boundaries.c.

2196{
2197 PetscErrorCode ierr;
2198 PetscBool is_any_periodic = PETSC_FALSE;
2199
2200 PetscFunctionBeginUser;
2201
2203
2204 for (int i = 0; i < 6; i++) {
2205 if (user->boundary_faces[i].mathematical_type == PERIODIC) {
2206 is_any_periodic = PETSC_TRUE;
2207 break;
2208 }
2209 }
2210
2211 if (!is_any_periodic) {
2212 LOG_ALLOW(GLOBAL,LOG_TRACE, "No periodic boundaries defined; skipping ApplyPeriodicBCs.\n");
2214 PetscFunctionReturn(0);
2215 }
2216
2217 LOG_ALLOW(GLOBAL, LOG_TRACE, "Applying periodic boundary conditions for all fields.\n");
2218
2219 // STEP 1: Synchronize periodic cell-centered fields in deterministic direction order.
2220 const FieldId cell_fields[] = {FIELD_ID_UCAT, FIELD_ID_P, FIELD_ID_NVERT};
2221 ierr = SynchronizePeriodicCellFields(user, 3, cell_fields); CHKERRQ(ierr);
2222
2223 /* A future temperature field must be catalogued before requesting its typed ghost update. */
2224
2225 // STEP 2: Synchronize persistent staggered endpoints and repair local
2226 // component-normal ghosts through UpdateLocalGhosts().
2227 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
2228 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); CHKERRQ(ierr);
2229
2230 // FUTURE EXTENSION: Add new cell fields through SynchronizePeriodicCellFields().
2231 /*
2232 if (user->solve_temperature) {
2233 const char *temperature_field[] = {"Temperature"};
2234 ierr = SynchronizePeriodicCellFields(user, 1, temperature_field); CHKERRQ(ierr);
2235 }
2236 */
2237
2239 PetscFunctionReturn(0);
2240}
PetscErrorCode SynchronizePeriodicStaggeredFields(UserCtx *user, PetscInt num_fields, const FieldId field_ids[])
Implementation of SynchronizePeriodicStaggeredFields().
@ FIELD_ID_NVERT
@ FIELD_ID_UCAT
@ FIELD_ID_UCONT
@ FIELD_ID_P
Here is the call graph for this function:
Here is the caller graph for this function:

◆ UpdateDummyCells()

PetscErrorCode UpdateDummyCells ( UserCtx *  user,
FieldId  field_id 
)

Fills the dummy cells on the non-periodic faces of one cell-centred field.

A cell-centred field stores cell c at index c+1; index 0 and the last index along each axis are dummy cells outside the domain. Stencils and node averages that reach across a boundary read them, so their value is the field's boundary condition:

Field Dummy value Condition it encodes
Ucat 2 Ubcs - interior face average equals the handler's boundary velocity
P the adjacent interior cell zero normal gradient
Psi the adjacent interior cell zero normal gradient (no scalar flux)

Any other field is refused: its condition must be chosen for the quantity before its dummy cells are filled (see 12. Derived Statistics At Non-Periodic Boundaries for why no default serves every quantity).

Pressure. Zero gradient is the condition the pressure solve already implies on every non-periodic face: the Poisson operator drops each boundary face's term, the dummy rows are identities with a zero right-hand side, and the projection corrects interior faces only. Nothing in the solve reads these values; filling them makes P_nodal and near-wall pressure gradients (the Cabot wall model) consistent with that condition, instead of averaging in a dummy value that stays at its initial zero. A pressure boundary condition (a far field, or a pressure outlet) will set its dummy value here as well, but that alone changes nothing the solve sees; see 6b. Pressure Boundary Conditions for what has to change with it.

Periodic faces are skipped: the periodic synchronizers own them. Only the flat part of each face is written (shrunken loop ranges); edges and corners are filled afterwards by UpdateCornerNodes from these values.

Parameters
userBlock context supplying the layout, face types, and Ubcs.
field_idCell-centred field to fill: FIELD_ID_UCAT, FIELD_ID_P, or FIELD_ID_PSI.
Returns
Zero on success; PETSC_ERR_ARG_WRONG for a field that is not cell-centred, PETSC_ERR_SUP for one with no boundary rule.

Fills the dummy cells on the non-periodic faces of one cell-centred field.

Full API contract is documented with the header declaration in include/Boundaries.h.

See also
UpdateDummyCells()

Definition at line 2250 of file Boundaries.c.

2251{
2252 DMDALocalInfo info = user->info;
2253 PetscInt xs = info.xs, xe = info.xs + info.xm;
2254 PetscInt ys = info.ys, ye = info.ys + info.ym;
2255 PetscInt zs = info.zs, ze = info.zs + info.zm;
2256 PetscInt mx = info.mx, my = info.my, mz = info.mz;
2257 FieldView view;
2258 PetscScalar ****field = NULL, ****ubcs = NULL;
2259 PetscInt dof;
2260 PetscBool from_boundary_value;
2261
2262 // --- Calculate shrunken loop ranges to avoid edges and corners ---
2263 PetscInt lxs = (xs == 0) ? xs + 1 : xs, lxe = (xe == mx) ? xe - 1 : xe;
2264 PetscInt lys = (ys == 0) ? ys + 1 : ys, lye = (ye == my) ? ye - 1 : ye;
2265 PetscInt lzs = (zs == 0) ? zs + 1 : zs, lze = (ze == mz) ? ze - 1 : ze;
2266
2267 PetscFunctionBeginUser;
2268 PetscCall(FieldGetView(user, field_id, &view));
2269 PetscCheck(view.descriptor->layout == FIELD_LAYOUT_CELL_CENTERED, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG,
2270 "UpdateDummyCells fills cell-centred fields; '%s' is not one.", view.descriptor->canonical_name);
2271 /* The dummy value is the quantity's boundary condition. Velocity takes the
2272 * boundary value the handlers set, placed so the face average equals it. Pressure
2273 * and the particle scalar take the adjacent cell's value: zero normal gradient. */
2274 PetscCheck(field_id == FIELD_ID_UCAT || field_id == FIELD_ID_P || field_id == FIELD_ID_PSI,
2275 PETSC_COMM_SELF, PETSC_ERR_SUP,
2276 "UpdateDummyCells has no boundary rule for '%s'; one must be chosen for the quantity "
2277 "before its dummy cells are filled.", view.descriptor->canonical_name);
2278 from_boundary_value = (PetscBool)(field_id == FIELD_ID_UCAT);
2279 dof = view.descriptor->dof;
2280
2281 PetscCall(DMDAVecGetArrayDOF(view.dm, view.global_vec, &field));
2282 if (from_boundary_value) PetscCall(DMDAVecGetArrayDOF(user->fda, user->Bcs.Ubcs, &ubcs));
2283
2284 // -X Face
2285 if (user->boundary_faces[BC_FACE_NEG_X].mathematical_type != PERIODIC && xs == 0) {
2286 for (PetscInt k = lzs; k < lze; k++) for (PetscInt j = lys; j < lye; j++) for (PetscInt c = 0; c < dof; c++)
2287 field[k][j][xs][c] = from_boundary_value ? 2.0 * ubcs[k][j][xs][c] - field[k][j][xs + 1][c]
2288 : field[k][j][xs + 1][c];
2289 }
2290 // +X Face
2291 if (user->boundary_faces[BC_FACE_POS_X].mathematical_type != PERIODIC && xe == mx) {
2292 for (PetscInt k = lzs; k < lze; k++) for (PetscInt j = lys; j < lye; j++) for (PetscInt c = 0; c < dof; c++)
2293 field[k][j][xe-1][c] = from_boundary_value ? 2.0 * ubcs[k][j][xe-1][c] - field[k][j][xe - 2][c]
2294 : field[k][j][xe - 2][c];
2295 }
2296 // -Y Face
2297 if (user->boundary_faces[BC_FACE_NEG_Y].mathematical_type != PERIODIC && ys == 0) {
2298 for (PetscInt k = lzs; k < lze; k++) for (PetscInt i = lxs; i < lxe; i++) for (PetscInt c = 0; c < dof; c++)
2299 field[k][ys][i][c] = from_boundary_value ? 2.0 * ubcs[k][ys][i][c] - field[k][ys + 1][i][c]
2300 : field[k][ys + 1][i][c];
2301 }
2302 // +Y Face
2303 if (user->boundary_faces[BC_FACE_POS_Y].mathematical_type != PERIODIC && ye == my) {
2304 for (PetscInt k = lzs; k < lze; k++) for (PetscInt i = lxs; i < lxe; i++) for (PetscInt c = 0; c < dof; c++)
2305 field[k][ye-1][i][c] = from_boundary_value ? 2.0 * ubcs[k][ye-1][i][c] - field[k][ye-2][i][c]
2306 : field[k][ye-2][i][c];
2307 }
2308 // -Z Face
2309 if (user->boundary_faces[BC_FACE_NEG_Z].mathematical_type != PERIODIC && zs == 0) {
2310 for (PetscInt j = lys; j < lye; j++) for (PetscInt i = lxs; i < lxe; i++) for (PetscInt c = 0; c < dof; c++)
2311 field[zs][j][i][c] = from_boundary_value ? 2.0 * ubcs[zs][j][i][c] - field[zs + 1][j][i][c]
2312 : field[zs + 1][j][i][c];
2313 }
2314 // +Z Face
2315 if (user->boundary_faces[BC_FACE_POS_Z].mathematical_type != PERIODIC && ze == mz) {
2316 for (PetscInt j = lys; j < lye; j++) for (PetscInt i = lxs; i < lxe; i++) for (PetscInt c = 0; c < dof; c++)
2317 field[ze-1][j][i][c] = from_boundary_value ? 2.0 * ubcs[ze-1][j][i][c] - field[ze-2][j][i][c]
2318 : field[ze-2][j][i][c];
2319 }
2320
2321 if (from_boundary_value) PetscCall(DMDAVecRestoreArrayDOF(user->fda, user->Bcs.Ubcs, &ubcs));
2322 PetscCall(DMDAVecRestoreArrayDOF(view.dm, view.global_vec, &field));
2323 PetscFunctionReturn(0);
2324}
FieldLayout layout
const FieldDescriptor * descriptor
PetscErrorCode FieldGetView(UserCtx *user, FieldId field_id, FieldView *view)
Resolve the existing DM and global/local vectors for one field.
@ FIELD_LAYOUT_CELL_CENTERED
const char * canonical_name
@ FIELD_ID_PSI
Non-owning runtime objects resolved for one field and UserCtx.
Vec Ubcs
Physical Cartesian velocity at boundary faces. Full 3D array but only boundary-face entries are meani...
Definition variables.h:149
Here is the call graph for this function:
Here is the caller graph for this function:

◆ UpdateCornerNodes()

PetscErrorCode UpdateCornerNodes ( UserCtx *  user,
FieldId  field_id 
)

Fills the edge and corner dummy cells of one cell-centred field by averaging.

Call after the face dummy cells are final, both periodic (the periodic synchronizers) and non-periodic (UpdateDummyCells). Each edge dummy cell becomes the mean of its two face-adjacent dummy cells; the edges along the Z faces are done first, so the corners the remaining edges pass through are averaged from finished edges. The rule is the same whatever the adjacent face types are.

Parameters
userBlock context supplying the layout.
field_idCell-centred field whose edges and corners are filled.
Returns
Zero on success; PETSC_ERR_ARG_WRONG for a field that is not cell-centred.

Fills the edge and corner dummy cells of one cell-centred field by averaging.

Full API contract is documented with the header declaration in include/Boundaries.h.

See also
UpdateCornerNodes()

Definition at line 2334 of file Boundaries.c.

2335{
2336 DMDALocalInfo info = user->info;
2337 PetscInt xs = info.xs, xe = info.xs + info.xm;
2338 PetscInt ys = info.ys, ye = info.ys + info.ym;
2339 PetscInt zs = info.zs, ze = info.zs + info.zm;
2340 PetscInt mx = info.mx, my = info.my, mz = info.mz;
2341 FieldView view;
2342 PetscScalar ****f = NULL;
2343 PetscInt dof;
2344
2345 PetscFunctionBeginUser;
2346 PetscCall(FieldGetView(user, field_id, &view));
2347 PetscCheck(view.descriptor->layout == FIELD_LAYOUT_CELL_CENTERED, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG,
2348 "UpdateCornerNodes fills cell-centred fields; '%s' is not one.", view.descriptor->canonical_name);
2349 dof = view.descriptor->dof;
2350 PetscCall(DMDAVecGetArrayDOF(view.dm, view.global_vec, &f));
2351
2352/* Each edge dummy cell is the mean of its two face-adjacent dummy cells. */
2353#define EDGE_AVERAGE(tk, tj, ti, ak, aj, ai, bk, bj, bi) \
2354 for (PetscInt c = 0; c < dof; c++) f[tk][tj][ti][c] = 0.5 * (f[ak][aj][ai][c] + f[bk][bj][bi][c])
2355
2356 // --- Update Edges and Corners by Averaging ---
2357 // The order of these blocks ensures that corners (where 3 faces meet) are
2358 // computed using data from edges (where 2 faces meet), which are computed first.
2359 // Edges connected to the -Z face (k=zs)
2360 if (zs == 0) {
2361 if (xs == 0) for (PetscInt j = ys; j < ye; j++) { EDGE_AVERAGE(zs, j, xs, zs+1, j, xs, zs, j, xs+1); }
2362 if (xe == mx) for (PetscInt j = ys; j < ye; j++) { EDGE_AVERAGE(zs, j, mx-1, zs+1, j, mx-1, zs, j, mx-2); }
2363 if (ys == 0) for (PetscInt i = xs; i < xe; i++) { EDGE_AVERAGE(zs, ys, i, zs+1, ys, i, zs, ys+1, i); }
2364 if (ye == my) for (PetscInt i = xs; i < xe; i++) { EDGE_AVERAGE(zs, my-1, i, zs+1, my-1, i, zs, my-2, i); }
2365 }
2366 // Edges connected to the +Z face (k=ze-1)
2367 if (ze == mz) {
2368 if (xs == 0) for (PetscInt j = ys; j < ye; j++) { EDGE_AVERAGE(mz-1, j, xs, mz-2, j, xs, mz-1, j, xs+1); }
2369 if (xe == mx) for (PetscInt j = ys; j < ye; j++) { EDGE_AVERAGE(mz-1, j, mx-1, mz-2, j, mx-1, mz-1, j, mx-2); }
2370 if (ys == 0) for (PetscInt i = xs; i < xe; i++) { EDGE_AVERAGE(mz-1, ys, i, mz-2, ys, i, mz-1, ys+1, i); }
2371 if (ye == my) for (PetscInt i = xs; i < xe; i++) { EDGE_AVERAGE(mz-1, my-1, i, mz-2, my-1, i, mz-1, my-2, i); }
2372 }
2373 // Remaining edges on the XY plane (that are not on Z faces)
2374 if (ys == 0) {
2375 if (xs == 0) for (PetscInt k = zs; k < ze; k++) { EDGE_AVERAGE(k, ys, xs, k, ys+1, xs, k, ys, xs+1); }
2376 if (xe == mx) for (PetscInt k = zs; k < ze; k++) { EDGE_AVERAGE(k, ys, mx-1, k, ys+1, mx-1, k, ys, mx-2); }
2377 }
2378 if (ye == my) {
2379 if (xs == 0) for (PetscInt k = zs; k < ze; k++) { EDGE_AVERAGE(k, my-1, xs, k, my-2, xs, k, my-1, xs+1); }
2380 if (xe == mx) for (PetscInt k = zs; k < ze; k++) { EDGE_AVERAGE(k, my-1, mx-1, k, my-2, mx-1, k, my-1, mx-2); }
2381 }
2382#undef EDGE_AVERAGE
2383
2384 PetscCall(DMDAVecRestoreArrayDOF(view.dm, view.global_vec, &f));
2385 PetscFunctionReturn(0);
2386}
#define EDGE_AVERAGE(tk, tj, ti, ak, aj, ai, bk, bj, bi)
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ApplyWallFunction()

PetscErrorCode ApplyWallFunction ( UserCtx *  user)

Applies wall function modeling to near-wall velocities for all wall-type boundaries.

This function implements log-law wall functions to model the near-wall velocity profile without fully resolving the viscous sublayer. It is applicable to ALL wall-type boundaries regardless of their specific boundary condition (no-slip, moving wall, slip, etc.), as determined by the mathematical_type being WALL.

MATHEMATICAL BACKGROUND: Wall functions bridge the gap between the wall (y=0) and the first computational cell center by using empirical log-law relationships:

  • Viscous sublayer (y+ < 11.81): u+ = y+
  • Log-law region (y+ > 11.81): u+ = (1/κ) * ln(E * y+) where u+ = u/u_Ï„, y+ = y*u_Ï„/ν, κ = 0.41 (von Karman constant), E = exp(κB)

IMPLEMENTATION DETAILS: Unlike standard boundary conditions that set ghost cell values, wall functions:

  1. Read velocity from the SECOND interior cell (i±2, j±2, k±2)
  2. Compute wall shear stress using log-law
  3. Modify velocity at the FIRST interior cell (i±1, j±1, k±1)
  4. Keep ghost cell boundary values (ubcs, ucont) at zero

WORKFLOW:

  • Called from ApplyBoundaryConditions after standard BC application
  • Operates on ucat (Cartesian velocity)
  • Updates ustar (friction velocity field) for diagnostics/turbulence models
  • Ghost cells remain zero; UpdateDummyCells handles extrapolation afterward

GEOMETRIC QUANTITIES: sb = wall-normal distance from wall to first interior cell center sc = wall-normal distance from wall to second interior cell center
These are computed from cell Jacobians (aj) and face area vectors

APPLICABILITY:

  • Requires simCtx->wallfunction = true
  • Only processes faces where mathematical_type == WALL
  • Skips solid-embedded cells (nvert >= 0.1)
Parameters
userThe UserCtx containing all simulation state and geometry
Returns
PetscErrorCode 0 on success
Note
This function modifies interior cell velocities, NOT ghost cells
Wall roughness (ks) is currently set to 1e-16 (smooth wall)
See also
wall_function_loglaw() in wallfunction.c for the actual log-law implementation
noslip() in wallfunction.c for the initial linear interpolation

Applies wall function modeling to near-wall velocities for all wall-type boundaries.

Local to this translation unit.

Definition at line 2507 of file Boundaries.c.

2508{
2509 PetscErrorCode ierr;
2510 SimCtx *simCtx = user->simCtx;
2511 DMDALocalInfo *info = &user->info;
2512
2513 PetscFunctionBeginUser;
2514
2515 // =========================================================================
2516 // STEP 0: Early exit if wall functions are disabled
2517 // =========================================================================
2518 if (!simCtx->wallfunction) {
2519 PetscFunctionReturn(0);
2520 }
2521
2522 LOG_ALLOW(LOCAL, LOG_DEBUG, "Processing wall function boundaries.\n");
2523
2524 /* One pass, one sample set: the state describes this pass and nothing earlier. */
2525 ierr = PetscMemzero(&user->wall_diagnostics, sizeof(user->wall_diagnostics)); CHKERRQ(ierr);
2526 /* Cells the model does not reach must not keep a stale wall viscosity from an
2527 earlier pass, so the field is cleared rather than accumulated into. */
2528 ierr = VecSet(user->Nu_Wall, 0.0); CHKERRQ(ierr);
2529
2530 // =========================================================================
2531 // STEP 1: Get read/write access to all necessary field arrays
2532 // =========================================================================
2533 Cmpnts ***velocity_cartesian; // Cartesian velocity (modified)
2534 Cmpnts ***velocity_contravariant; // Contravariant velocity (set to zero at walls)
2535 Cmpnts ***velocity_boundary; // Boundary condition velocity (kept at zero)
2536 Cmpnts ***csi, ***eta, ***zet; // Metric tensor components (face normals)
2537 PetscReal ***node_vertex_flag; // Fluid/solid indicator (0=fluid, 1=solid)
2538 PetscReal ***cell_jacobian; // Grid Jacobian (1/volume)
2539 PetscReal ***wall_eddy_viscosity; // Effective wall eddy viscosity (written)
2540 PetscReal ***friction_velocity;
2541 PetscReal ***wall_pressure = NULL; // u_tau (friction velocity field)
2542
2543 ierr = DMDAVecGetArray(user->fda, user->Ucat, &velocity_cartesian); CHKERRQ(ierr);
2544 ierr = DMDAVecGetArray(user->fda, user->Ucont, &velocity_contravariant); CHKERRQ(ierr);
2545 ierr = DMDAVecGetArray(user->fda, user->Bcs.Ubcs, &velocity_boundary); CHKERRQ(ierr);
2546 ierr = DMDAVecGetArrayRead(user->fda, user->lCsi, (const Cmpnts***)&csi); CHKERRQ(ierr);
2547 ierr = DMDAVecGetArrayRead(user->fda, user->lEta, (const Cmpnts***)&eta); CHKERRQ(ierr);
2548 ierr = DMDAVecGetArrayRead(user->fda, user->lZet, (const Cmpnts***)&zet); CHKERRQ(ierr);
2549 ierr = DMDAVecGetArrayRead(user->da, user->lNvert, (const PetscReal***)&node_vertex_flag); CHKERRQ(ierr);
2550 ierr = DMDAVecGetArrayRead(user->da, user->lAj, (const PetscReal***)&cell_jacobian); CHKERRQ(ierr);
2551 /* The global view, like the Ucat this pass corrects: every cell it writes is one
2552 this rank owns, and the ghosted image is refreshed by the caller afterwards. */
2553 ierr = DMDAVecGetArray(user->da, user->Friction_Velocity, &friction_velocity); CHKERRQ(ierr);
2554 ierr = DMDAVecGetArray(user->da, user->Nu_Wall, &wall_eddy_viscosity); CHKERRQ(ierr);
2555 /* Read-only pressure for the wall models that need its gradient; Cabot is the only
2556 one that does, and it reads the previous projection's field. */
2557 ierr = DMDAVecGetArrayRead(user->da, user->lP, (const PetscReal ***)&wall_pressure); CHKERRQ(ierr);
2558
2559 // =========================================================================
2560 // STEP 2: Define loop bounds (owned portion of the grid for this MPI rank)
2561 // =========================================================================
2562 PetscInt grid_start_i = info->xs, grid_end_i = info->xs + info->xm;
2563 PetscInt grid_start_j = info->ys, grid_end_j = info->ys + info->ym;
2564 PetscInt grid_start_k = info->zs, grid_end_k = info->zs + info->zm;
2565 PetscInt grid_size_i = info->mx, grid_size_j = info->my, grid_size_k = info->mz;
2566
2567 // Shrunken loop bounds: exclude domain edges and corners to avoid double-counting
2568 PetscInt loop_start_i = grid_start_i, loop_end_i = grid_end_i;
2569 PetscInt loop_start_j = grid_start_j, loop_end_j = grid_end_j;
2570 PetscInt loop_start_k = grid_start_k, loop_end_k = grid_end_k;
2571
2572 if (grid_start_i == 0) loop_start_i = grid_start_i + 1;
2573 if (grid_end_i == grid_size_i) loop_end_i = grid_end_i - 1;
2574 if (grid_start_j == 0) loop_start_j = grid_start_j + 1;
2575 if (grid_end_j == grid_size_j) loop_end_j = grid_end_j - 1;
2576 if (grid_start_k == 0) loop_start_k = grid_start_k + 1;
2577 if (grid_end_k == grid_size_k) loop_end_k = grid_end_k - 1;
2578
2579 // Wall roughness parameter (smooth wall by default, configurable via -wall_roughness).
2580 const PetscReal wall_roughness_height = user->simCtx->wall_roughness_height;
2581
2582 // =========================================================================
2583 // STEP 3: Process each of the 6 domain faces
2584 // =========================================================================
2585 for (int face_index = 0; face_index < 6; face_index++) {
2586 BCFace current_face_id = (BCFace)face_index;
2587 BoundaryFaceConfig *face_config = &user->boundary_faces[current_face_id];
2588
2589 // Only process faces that are mathematical walls (applies to no-slip, moving, slip, etc.)
2590 if (face_config->mathematical_type != WALL) {
2591 continue;
2592 }
2593
2594 // Check if this MPI rank owns part of this face
2595 PetscBool rank_owns_this_face;
2596 ierr = CanRankServiceFace(info, user->IM, user->JM, user->KM,
2597 current_face_id, &rank_owns_this_face); CHKERRQ(ierr);
2598
2599 if (!rank_owns_this_face) {
2600 continue;
2601 }
2602
2603 LOG_ALLOW(LOCAL, LOG_TRACE, "Processing Face %d (%s)\n",
2604 current_face_id, BCFaceToString(current_face_id));
2605
2606 // =====================================================================
2607 // Process each face with appropriate indexing
2608 // =====================================================================
2609 switch(current_face_id) {
2610
2611 // =================================================================
2612 // NEGATIVE X FACE (i = 0, normal points in +X direction)
2613 // =================================================================
2614 case BC_FACE_NEG_X: {
2615 if (grid_start_i == 0) {
2616 const PetscInt ghost_cell_index = grid_start_i;
2617 const PetscInt first_interior_cell = grid_start_i + 1;
2618 const PetscInt second_interior_cell = grid_start_i + 2;
2619
2620 for (PetscInt k = loop_start_k; k < loop_end_k; k++) {
2621 for (PetscInt j = loop_start_j; j < loop_end_j; j++) {
2622
2623 // Skip if this is a solid cell (embedded boundary)
2624 if (node_vertex_flag[k][j][first_interior_cell] < 0.1) {
2625
2626 // Calculate face area from contravariant metric tensor
2627 PetscReal face_area = sqrt(
2628 csi[k][j][ghost_cell_index].x * csi[k][j][ghost_cell_index].x +
2629 csi[k][j][ghost_cell_index].y * csi[k][j][ghost_cell_index].y +
2630 csi[k][j][ghost_cell_index].z * csi[k][j][ghost_cell_index].z
2631 );
2632
2633 // Compute wall-normal distances using cell Jacobians
2634 // sb = distance from wall to first interior cell center
2635 // sc = distance from wall to second interior cell center
2636 PetscReal distance_to_first_cell = 0.5 / cell_jacobian[k][j][first_interior_cell] / face_area;
2637 PetscReal distance_to_second_cell = 2.0 * distance_to_first_cell +
2638 0.5 / cell_jacobian[k][j][second_interior_cell] / face_area;
2639
2640 // Compute unit normal vector pointing INTO the domain
2641 PetscReal wall_normal[3];
2642 wall_normal[0] = csi[k][j][ghost_cell_index].x / face_area;
2643 wall_normal[1] = csi[k][j][ghost_cell_index].y / face_area;
2644 wall_normal[2] = csi[k][j][ghost_cell_index].z / face_area;
2645
2646 // Define velocities for wall function calculation
2647 Cmpnts wall_velocity; // Ua = velocity at wall (zero for stationary wall)
2648 Cmpnts reference_velocity; // Uc = velocity at second interior cell
2649
2650 wall_velocity.x = wall_velocity.y = wall_velocity.z = 0.0;
2651 reference_velocity = velocity_cartesian[k][j][second_interior_cell];
2652
2653 // Step 1: Linear interpolation (provides initial guess)
2654 noslip(user, distance_to_second_cell, distance_to_first_cell,
2655 wall_velocity, reference_velocity,
2656 &velocity_cartesian[k][j][first_interior_cell],
2657 wall_normal[0], wall_normal[1], wall_normal[2]);
2658
2659 // Step 2: Apply log-law correction (improves near-wall velocity)
2660 ierr = ApplyWallModelAtCell(user, wall_pressure, wall_eddy_viscosity,
2661 first_interior_cell, j, k,
2662 wall_roughness_height,
2663 distance_to_second_cell, distance_to_first_cell,
2664 wall_velocity, reference_velocity,
2665 &velocity_cartesian[k][j][first_interior_cell],
2666 &friction_velocity[k][j][first_interior_cell],
2667 wall_normal[0], wall_normal[1], wall_normal[2]); CHKERRQ(ierr);
2668
2669 // Ensure ghost cell BC remains zero (required for proper extrapolation)
2670 velocity_boundary[k][j][ghost_cell_index].x = 0.0;
2671 velocity_boundary[k][j][ghost_cell_index].y = 0.0;
2672 velocity_boundary[k][j][ghost_cell_index].z = 0.0;
2673 velocity_contravariant[k][j][ghost_cell_index].x = 0.0;
2674 }
2675 }
2676 }
2677 }
2678 } break;
2679
2680 // =================================================================
2681 // POSITIVE X FACE (i = mx-1, normal points in -X direction)
2682 // =================================================================
2683 case BC_FACE_POS_X: {
2684 if (grid_end_i == grid_size_i) {
2685 const PetscInt ghost_cell_index = grid_end_i - 1;
2686 const PetscInt first_interior_cell = grid_end_i - 2;
2687 const PetscInt second_interior_cell = grid_end_i - 3;
2688
2689 for (PetscInt k = loop_start_k; k < loop_end_k; k++) {
2690 for (PetscInt j = loop_start_j; j < loop_end_j; j++) {
2691
2692 if (node_vertex_flag[k][j][first_interior_cell] < 0.1) {
2693
2694 PetscReal face_area = sqrt(
2695 csi[k][j][first_interior_cell].x * csi[k][j][first_interior_cell].x +
2696 csi[k][j][first_interior_cell].y * csi[k][j][first_interior_cell].y +
2697 csi[k][j][first_interior_cell].z * csi[k][j][first_interior_cell].z
2698 );
2699
2700 PetscReal distance_to_first_cell = 0.5 / cell_jacobian[k][j][first_interior_cell] / face_area;
2701 PetscReal distance_to_second_cell = 2.0 * distance_to_first_cell +
2702 0.5 / cell_jacobian[k][j][second_interior_cell] / face_area;
2703
2704 // Note: Normal flipped for +X face to point INTO domain
2705 PetscReal wall_normal[3];
2706 wall_normal[0] = -csi[k][j][first_interior_cell].x / face_area;
2707 wall_normal[1] = -csi[k][j][first_interior_cell].y / face_area;
2708 wall_normal[2] = -csi[k][j][first_interior_cell].z / face_area;
2709
2710 Cmpnts wall_velocity, reference_velocity;
2711 wall_velocity.x = wall_velocity.y = wall_velocity.z = 0.0;
2712 reference_velocity = velocity_cartesian[k][j][second_interior_cell];
2713
2714 noslip(user, distance_to_second_cell, distance_to_first_cell,
2715 wall_velocity, reference_velocity,
2716 &velocity_cartesian[k][j][first_interior_cell],
2717 wall_normal[0], wall_normal[1], wall_normal[2]);
2718
2719 ierr = ApplyWallModelAtCell(user, wall_pressure, wall_eddy_viscosity,
2720 first_interior_cell, j, k,
2721 wall_roughness_height,
2722 distance_to_second_cell, distance_to_first_cell,
2723 wall_velocity, reference_velocity,
2724 &velocity_cartesian[k][j][first_interior_cell],
2725 &friction_velocity[k][j][first_interior_cell],
2726 wall_normal[0], wall_normal[1], wall_normal[2]); CHKERRQ(ierr);
2727
2728 velocity_boundary[k][j][ghost_cell_index].x = 0.0;
2729 velocity_boundary[k][j][ghost_cell_index].y = 0.0;
2730 velocity_boundary[k][j][ghost_cell_index].z = 0.0;
2731 velocity_contravariant[k][j][first_interior_cell].x = 0.0;
2732 }
2733 }
2734 }
2735 }
2736 } break;
2737
2738 // =================================================================
2739 // NEGATIVE Y FACE (j = 0, normal points in +Y direction)
2740 // =================================================================
2741 case BC_FACE_NEG_Y: {
2742 if (grid_start_j == 0) {
2743 const PetscInt ghost_cell_index = grid_start_j;
2744 const PetscInt first_interior_cell = grid_start_j + 1;
2745 const PetscInt second_interior_cell = grid_start_j + 2;
2746
2747 for (PetscInt k = loop_start_k; k < loop_end_k; k++) {
2748 for (PetscInt i = loop_start_i; i < loop_end_i; i++) {
2749
2750 if (node_vertex_flag[k][first_interior_cell][i] < 0.1) {
2751
2752 PetscReal face_area = sqrt(
2753 eta[k][ghost_cell_index][i].x * eta[k][ghost_cell_index][i].x +
2754 eta[k][ghost_cell_index][i].y * eta[k][ghost_cell_index][i].y +
2755 eta[k][ghost_cell_index][i].z * eta[k][ghost_cell_index][i].z
2756 );
2757
2758 PetscReal distance_to_first_cell = 0.5 / cell_jacobian[k][first_interior_cell][i] / face_area;
2759 PetscReal distance_to_second_cell = 2.0 * distance_to_first_cell +
2760 0.5 / cell_jacobian[k][second_interior_cell][i] / face_area;
2761
2762 PetscReal wall_normal[3];
2763 wall_normal[0] = eta[k][ghost_cell_index][i].x / face_area;
2764 wall_normal[1] = eta[k][ghost_cell_index][i].y / face_area;
2765 wall_normal[2] = eta[k][ghost_cell_index][i].z / face_area;
2766
2767 Cmpnts wall_velocity, reference_velocity;
2768 wall_velocity.x = wall_velocity.y = wall_velocity.z = 0.0;
2769 reference_velocity = velocity_cartesian[k][second_interior_cell][i];
2770
2771 noslip(user, distance_to_second_cell, distance_to_first_cell,
2772 wall_velocity, reference_velocity,
2773 &velocity_cartesian[k][first_interior_cell][i],
2774 wall_normal[0], wall_normal[1], wall_normal[2]);
2775
2776 ierr = ApplyWallModelAtCell(user, wall_pressure, wall_eddy_viscosity,
2777 i, first_interior_cell, k,
2778 wall_roughness_height,
2779 distance_to_second_cell, distance_to_first_cell,
2780 wall_velocity, reference_velocity,
2781 &velocity_cartesian[k][first_interior_cell][i],
2782 &friction_velocity[k][first_interior_cell][i],
2783 wall_normal[0], wall_normal[1], wall_normal[2]); CHKERRQ(ierr);
2784
2785 velocity_boundary[k][ghost_cell_index][i].x = 0.0;
2786 velocity_boundary[k][ghost_cell_index][i].y = 0.0;
2787 velocity_boundary[k][ghost_cell_index][i].z = 0.0;
2788 velocity_contravariant[k][ghost_cell_index][i].y = 0.0;
2789 }
2790 }
2791 }
2792 }
2793 } break;
2794
2795 // =================================================================
2796 // POSITIVE Y FACE (j = my-1, normal points in -Y direction)
2797 // =================================================================
2798 case BC_FACE_POS_Y: {
2799 if (grid_end_j == grid_size_j) {
2800 const PetscInt ghost_cell_index = grid_end_j - 1;
2801 const PetscInt first_interior_cell = grid_end_j - 2;
2802 const PetscInt second_interior_cell = grid_end_j - 3;
2803
2804 for (PetscInt k = loop_start_k; k < loop_end_k; k++) {
2805 for (PetscInt i = loop_start_i; i < loop_end_i; i++) {
2806
2807 if (node_vertex_flag[k][first_interior_cell][i] < 0.1) {
2808
2809 PetscReal face_area = sqrt(
2810 eta[k][first_interior_cell][i].x * eta[k][first_interior_cell][i].x +
2811 eta[k][first_interior_cell][i].y * eta[k][first_interior_cell][i].y +
2812 eta[k][first_interior_cell][i].z * eta[k][first_interior_cell][i].z
2813 );
2814
2815 PetscReal distance_to_first_cell = 0.5 / cell_jacobian[k][first_interior_cell][i] / face_area;
2816 PetscReal distance_to_second_cell = 2.0 * distance_to_first_cell +
2817 0.5 / cell_jacobian[k][second_interior_cell][i] / face_area;
2818
2819 PetscReal wall_normal[3];
2820 wall_normal[0] = -eta[k][first_interior_cell][i].x / face_area;
2821 wall_normal[1] = -eta[k][first_interior_cell][i].y / face_area;
2822 wall_normal[2] = -eta[k][first_interior_cell][i].z / face_area;
2823
2824 Cmpnts wall_velocity, reference_velocity;
2825 wall_velocity.x = wall_velocity.y = wall_velocity.z = 0.0;
2826 reference_velocity = velocity_cartesian[k][second_interior_cell][i];
2827
2828 noslip(user, distance_to_second_cell, distance_to_first_cell,
2829 wall_velocity, reference_velocity,
2830 &velocity_cartesian[k][first_interior_cell][i],
2831 wall_normal[0], wall_normal[1], wall_normal[2]);
2832
2833 ierr = ApplyWallModelAtCell(user, wall_pressure, wall_eddy_viscosity,
2834 i, first_interior_cell, k,
2835 wall_roughness_height,
2836 distance_to_second_cell, distance_to_first_cell,
2837 wall_velocity, reference_velocity,
2838 &velocity_cartesian[k][first_interior_cell][i],
2839 &friction_velocity[k][first_interior_cell][i],
2840 wall_normal[0], wall_normal[1], wall_normal[2]); CHKERRQ(ierr);
2841
2842 velocity_boundary[k][ghost_cell_index][i].x = 0.0;
2843 velocity_boundary[k][ghost_cell_index][i].y = 0.0;
2844 velocity_boundary[k][ghost_cell_index][i].z = 0.0;
2845 velocity_contravariant[k][first_interior_cell][i].y = 0.0;
2846 }
2847 }
2848 }
2849 }
2850 } break;
2851
2852 // =================================================================
2853 // NEGATIVE Z FACE (k = 0, normal points in +Z direction)
2854 // =================================================================
2855 case BC_FACE_NEG_Z: {
2856 if (grid_start_k == 0) {
2857 const PetscInt ghost_cell_index = grid_start_k;
2858 const PetscInt first_interior_cell = grid_start_k + 1;
2859 const PetscInt second_interior_cell = grid_start_k + 2;
2860
2861 for (PetscInt j = loop_start_j; j < loop_end_j; j++) {
2862 for (PetscInt i = loop_start_i; i < loop_end_i; i++) {
2863
2864 if (node_vertex_flag[first_interior_cell][j][i] < 0.1) {
2865
2866 PetscReal face_area = sqrt(
2867 zet[ghost_cell_index][j][i].x * zet[ghost_cell_index][j][i].x +
2868 zet[ghost_cell_index][j][i].y * zet[ghost_cell_index][j][i].y +
2869 zet[ghost_cell_index][j][i].z * zet[ghost_cell_index][j][i].z
2870 );
2871
2872 PetscReal distance_to_first_cell = 0.5 / cell_jacobian[first_interior_cell][j][i] / face_area;
2873 PetscReal distance_to_second_cell = 2.0 * distance_to_first_cell +
2874 0.5 / cell_jacobian[second_interior_cell][j][i] / face_area;
2875
2876 PetscReal wall_normal[3];
2877 wall_normal[0] = zet[ghost_cell_index][j][i].x / face_area;
2878 wall_normal[1] = zet[ghost_cell_index][j][i].y / face_area;
2879 wall_normal[2] = zet[ghost_cell_index][j][i].z / face_area;
2880
2881 Cmpnts wall_velocity, reference_velocity;
2882 wall_velocity.x = wall_velocity.y = wall_velocity.z = 0.0;
2883 reference_velocity = velocity_cartesian[second_interior_cell][j][i];
2884
2885 noslip(user, distance_to_second_cell, distance_to_first_cell,
2886 wall_velocity, reference_velocity,
2887 &velocity_cartesian[first_interior_cell][j][i],
2888 wall_normal[0], wall_normal[1], wall_normal[2]);
2889
2890 ierr = ApplyWallModelAtCell(user, wall_pressure, wall_eddy_viscosity,
2891 i, j, first_interior_cell,
2892 wall_roughness_height,
2893 distance_to_second_cell, distance_to_first_cell,
2894 wall_velocity, reference_velocity,
2895 &velocity_cartesian[first_interior_cell][j][i],
2896 &friction_velocity[first_interior_cell][j][i],
2897 wall_normal[0], wall_normal[1], wall_normal[2]); CHKERRQ(ierr);
2898
2899 velocity_boundary[ghost_cell_index][j][i].x = 0.0;
2900 velocity_boundary[ghost_cell_index][j][i].y = 0.0;
2901 velocity_boundary[ghost_cell_index][j][i].z = 0.0;
2902 velocity_contravariant[ghost_cell_index][j][i].z = 0.0;
2903 }
2904 }
2905 }
2906 }
2907 } break;
2908
2909 // =================================================================
2910 // POSITIVE Z FACE (k = mz-1, normal points in -Z direction)
2911 // =================================================================
2912 case BC_FACE_POS_Z: {
2913 if (grid_end_k == grid_size_k) {
2914 const PetscInt ghost_cell_index = grid_end_k - 1;
2915 const PetscInt first_interior_cell = grid_end_k - 2;
2916 const PetscInt second_interior_cell = grid_end_k - 3;
2917
2918 for (PetscInt j = loop_start_j; j < loop_end_j; j++) {
2919 for (PetscInt i = loop_start_i; i < loop_end_i; i++) {
2920
2921 if (node_vertex_flag[first_interior_cell][j][i] < 0.1) {
2922
2923 PetscReal face_area = sqrt(
2924 zet[first_interior_cell][j][i].x * zet[first_interior_cell][j][i].x +
2925 zet[first_interior_cell][j][i].y * zet[first_interior_cell][j][i].y +
2926 zet[first_interior_cell][j][i].z * zet[first_interior_cell][j][i].z
2927 );
2928
2929 PetscReal distance_to_first_cell = 0.5 / cell_jacobian[first_interior_cell][j][i] / face_area;
2930 PetscReal distance_to_second_cell = 2.0 * distance_to_first_cell +
2931 0.5 / cell_jacobian[second_interior_cell][j][i] / face_area;
2932
2933 PetscReal wall_normal[3];
2934 wall_normal[0] = -zet[first_interior_cell][j][i].x / face_area;
2935 wall_normal[1] = -zet[first_interior_cell][j][i].y / face_area;
2936 wall_normal[2] = -zet[first_interior_cell][j][i].z / face_area;
2937
2938 Cmpnts wall_velocity, reference_velocity;
2939 wall_velocity.x = wall_velocity.y = wall_velocity.z = 0.0;
2940 reference_velocity = velocity_cartesian[second_interior_cell][j][i];
2941
2942 noslip(user, distance_to_second_cell, distance_to_first_cell,
2943 wall_velocity, reference_velocity,
2944 &velocity_cartesian[first_interior_cell][j][i],
2945 wall_normal[0], wall_normal[1], wall_normal[2]);
2946
2947 ierr = ApplyWallModelAtCell(user, wall_pressure, wall_eddy_viscosity,
2948 i, j, first_interior_cell,
2949 wall_roughness_height,
2950 distance_to_second_cell, distance_to_first_cell,
2951 wall_velocity, reference_velocity,
2952 &velocity_cartesian[first_interior_cell][j][i],
2953 &friction_velocity[first_interior_cell][j][i],
2954 wall_normal[0], wall_normal[1], wall_normal[2]); CHKERRQ(ierr);
2955
2956 velocity_boundary[ghost_cell_index][j][i].x = 0.0;
2957 velocity_boundary[ghost_cell_index][j][i].y = 0.0;
2958 velocity_boundary[ghost_cell_index][j][i].z = 0.0;
2959 velocity_contravariant[first_interior_cell][j][i].z = 0.0;
2960 }
2961 }
2962 }
2963 }
2964 } break;
2965 }
2966 }
2967
2968 // =========================================================================
2969 // STEP 4: Restore all arrays and release memory
2970 // =========================================================================
2971 ierr = DMDAVecRestoreArray(user->fda, user->Ucat, &velocity_cartesian); CHKERRQ(ierr);
2972 ierr = DMDAVecRestoreArray(user->fda, user->Ucont, &velocity_contravariant); CHKERRQ(ierr);
2973 ierr = DMDAVecRestoreArray(user->fda, user->Bcs.Ubcs, &velocity_boundary); CHKERRQ(ierr);
2974 ierr = DMDAVecRestoreArrayRead(user->fda, user->lCsi, (const Cmpnts***)&csi); CHKERRQ(ierr);
2975 ierr = DMDAVecRestoreArrayRead(user->fda, user->lEta, (const Cmpnts***)&eta); CHKERRQ(ierr);
2976 ierr = DMDAVecRestoreArrayRead(user->fda, user->lZet, (const Cmpnts***)&zet); CHKERRQ(ierr);
2977 ierr = DMDAVecRestoreArrayRead(user->da, user->lNvert, (const PetscReal***)&node_vertex_flag); CHKERRQ(ierr);
2978 ierr = DMDAVecRestoreArrayRead(user->da, user->lAj, (const PetscReal***)&cell_jacobian); CHKERRQ(ierr);
2979 ierr = DMDAVecRestoreArrayRead(user->da, user->lP, (const PetscReal ***)&wall_pressure); CHKERRQ(ierr);
2980 ierr = DMDAVecRestoreArray(user->da, user->Nu_Wall, &wall_eddy_viscosity); CHKERRQ(ierr);
2981 ierr = DMDAVecRestoreArray(user->da, user->Friction_Velocity, &friction_velocity); CHKERRQ(ierr);
2982
2983 /* The correction wrote the global view; refresh the ghosted image here so that every
2984 caller sees a consistent field rather than each remembering to do it. */
2985 ierr = UpdateLocalGhosts(user, FIELD_ID_U_TAU); CHKERRQ(ierr);
2986 ierr = UpdateLocalGhosts(user, FIELD_ID_NU_WALL); CHKERRQ(ierr);
2987
2988 LOG_ALLOW(LOCAL, LOG_DEBUG, "Complete.\n");
2989
2990 PetscFunctionReturn(0);
2991}
static PetscErrorCode ApplyWallModelAtCell(UserCtx *user, PetscReal ***pressure, PetscReal ***wall_eddy_viscosity, PetscInt i, PetscInt j, PetscInt k, PetscReal roughness_height, PetscReal distance_reference, PetscReal distance_boundary, Cmpnts velocity_wall, Cmpnts velocity_reference, Cmpnts *velocity_boundary, PetscReal *friction_velocity, PetscReal normal_x, PetscReal normal_y, PetscReal normal_z)
Applies the configured wall model at one near-wall cell.
PetscErrorCode CanRankServiceFace(const DMDALocalInfo *info, PetscInt IM_nodes_global, PetscInt JM_nodes_global, PetscInt KM_nodes_global, BCFace face_id, PetscBool *can_service_out)
Implementation of CanRankServiceFace().
Definition Boundaries.c:127
@ FIELD_ID_U_TAU
@ FIELD_ID_NU_WALL
@ WALL
Definition variables.h:312
Vec lNvert
Definition variables.h:1113
PetscInt KM
Definition variables.h:1088
Vec Nu_Wall
Definition variables.h:1110
Vec Ucont
Definition variables.h:1113
PetscReal wall_roughness_height
Definition variables.h:947
PetscScalar z
Definition variables.h:122
PetscInt JM
Definition variables.h:1088
PetscInt wallfunction
Enable wall functions on WALL faces.
Definition variables.h:985
Vec Friction_Velocity
Definition variables.h:1106
PetscScalar y
Definition variables.h:122
PetscInt IM
Definition variables.h:1088
WallModelDiagnosticsState wall_diagnostics
Near-wall statistics from the last wall-model pass.
Definition variables.h:1158
void noslip(UserCtx *user, double distance_reference, double distance_boundary, Cmpnts velocity_wall, Cmpnts velocity_reference, Cmpnts *velocity_boundary, double normal_x, double normal_y, double normal_z)
Applies no-slip wall boundary condition with linear interpolation.
Here is the call graph for this function:
Here is the caller graph for this function:

◆ LogWallModelDiagnostics()

PetscErrorCode LogWallModelDiagnostics ( UserCtx *  user)

Appends one row of near-wall statistics to <run.analysis.metrics>/wall_model.csv.

Reduces the state ApplyWallFunction accumulated over the wall faces this rank owns and writes the run-wide row from rank 0. y+ is the column that matters: it says whether the first cell sits where the selected law is valid, and no stored field can answer that after the fact.

A no-op when no wall model is active. When a wall model is active but no cell was corrected - wall functions enabled on a case with no WALL face - it warns and writes nothing rather than emitting a row of zeros.

Collective on the block's communicator; call it on every rank, once per step, after ApplyWallFunction.

Parameters
[in]userBlock context whose wall-model pass has already run this step.
Returns
PetscErrorCode 0 on success.

Appends one row of near-wall statistics to <run.analysis.metrics>/wall_model.csv.

Full API contract is documented with the header declaration in include/Boundaries.h.

Definition at line 3000 of file Boundaries.c.

3001{
3002 SimCtx *simCtx = user->simCtx;
3003 const WallModelDiagnosticsState *state = &user->wall_diagnostics;
3004 MPI_Comm comm;
3005
3006 /* Index 0..3: cell count, u_tau sum, u_tau^2 sum, y+ sum. Index 4..5: wall-distance
3007 and wall-viscosity sums. One collective rather than six. */
3008 PetscReal local_sum[6], global_sum[6];
3009 PetscReal local_max[2], global_max[2];
3010 PetscReal local_min, global_min;
3011
3012 PetscFunctionBeginUser;
3013
3014 if (!simCtx->wallfunction) PetscFunctionReturn(0);
3015
3016 PetscCall(PetscObjectGetComm((PetscObject)user->da, &comm));
3017
3018 local_sum[0] = (PetscReal)state->cells;
3019 local_sum[1] = state->friction_velocity_sum;
3020 local_sum[2] = state->friction_velocity_sq;
3021 local_sum[3] = state->y_plus_sum;
3022 local_sum[4] = state->wall_distance_sum;
3023 local_sum[5] = state->wall_viscosity_sum;
3024 local_max[0] = (state->cells > 0) ? state->friction_velocity_max : 0.0;
3025 local_max[1] = (state->cells > 0) ? state->y_plus_max : 0.0;
3026 /* A rank that owns no wall face must not win the minimum with a zero it never
3027 measured, so it contributes the identity instead. */
3028 local_min = (state->cells > 0) ? state->friction_velocity_min : PETSC_MAX_REAL;
3029
3030 PetscCallMPI(MPI_Allreduce(local_sum, global_sum, 6, MPIU_REAL, MPI_SUM, comm));
3031 PetscCallMPI(MPI_Allreduce(local_max, global_max, 2, MPIU_REAL, MPI_MAX, comm));
3032 PetscCallMPI(MPI_Allreduce(&local_min, &global_min, 1, MPIU_REAL, MPI_MIN, comm));
3033
3034 if (simCtx->rank == 0) {
3035 const PetscReal cells = global_sum[0];
3036 FILE *file = NULL;
3037 PetscReal mean, mean_square, variance;
3038
3039 /* No wall face anywhere is a configuration fact, not a data point: a run with
3040 wall functions enabled and no WALL boundary would otherwise emit a row of
3041 zeros that reads like a converged answer. */
3042 if (cells <= 0.0) {
3044 "Wall model is enabled but no WALL face was corrected this step; "
3045 "no diagnostics written.\n");
3046 PetscFunctionReturn(0);
3047 }
3048
3049 mean = global_sum[1] / cells;
3050 mean_square = global_sum[2] / cells;
3051 variance = PetscMax(mean_square - mean * mean, 0.0);
3052
3053 PetscCall(PicurvOpenDiagnosticsCsv(simCtx, "wall_model.csv",
3054 "step,time,wall_cells,u_tau_mean,u_tau_rms,"
3055 "u_tau_min,u_tau_max,y_plus_mean,y_plus_max,"
3056 "wall_distance_mean,nu_wall_over_nu_mean,physical_time", &file));
3057 PetscReal physical_time = 0.0;
3058 PetscCall(PicurvPhysicalTime(simCtx, simCtx->ti, &physical_time));
3059 fprintf(file,
3060 "%d,%.6e,%d,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e\n",
3061 (int)simCtx->step, (double)simCtx->ti, (int)cells,
3062 (double)mean, (double)PetscSqrtReal(variance),
3063 (double)global_min, (double)global_max[0],
3064 (double)(global_sum[3] / cells), (double)global_max[1],
3065 (double)(global_sum[4] / cells),
3066 (double)(global_sum[5] / cells * simCtx->ren), (double)physical_time);
3067 PetscCheck(fclose(file) == 0, PETSC_COMM_SELF, PETSC_ERR_FILE_WRITE,
3068 "Unable to close the wall-model diagnostics file.");
3069
3071 " Wall model (%s): u_tau=%.4e, y+ (mean)=%.2f, y+ (max)=%.2f over %d cell(s)\n",
3073 (double)mean, (double)(global_sum[3] / cells),
3074 (double)global_max[1], (int)cells);
3075 }
3076
3077 /* Whether the first cell sits where the selected law is valid is a property of the
3078 mesh, not of the configuration, so it cannot be settled before the grid exists.
3079 It is checked here instead, and a run that stays outside the range is stopped
3080 rather than left to spend its walltime producing a wall stress the law cannot
3081 support. */
3082 {
3083 /* Consecutive samples tolerated outside the range. A startup transient or a
3084 brief excursion is not worth ending a run over; a mesh that is simply wrong
3085 never comes back. */
3086 const PetscInt PICURV_WALL_YPLUS_GRACE_SAMPLES = 10;
3087 const PetscReal y_plus_mean = (global_sum[0] > 0.0) ? global_sum[3] / global_sum[0] : 0.0;
3088 PetscReal lower = 0.0, upper = 300.0;
3089 const char *range_reason = NULL;
3090
3091 switch ((WallFunctionModel)simCtx->wallfunction) {
3093 /* Below 30 the first cell is under the logarithmic region; above 300 the
3094 law's own implementation reports no valid branch and the correction
3095 silently falls back to leaving the velocity alone. */
3096 lower = 30.0;
3097 upper = 300.0;
3098 range_reason = "the logarithmic region the law describes";
3099 break;
3101 /* The two-layer form has a valid branch below y+ = 11.81, so only the upper
3102 end is a limit. */
3103 lower = 0.0;
3104 upper = 300.0;
3105 range_reason = "the region the power law describes";
3106 break;
3107 default:
3108 /* Cabot integrates across the wall layer, so it has no lower bound worth
3109 asserting; a first cell far outside the layer leaves its ODE nothing to
3110 integrate over. */
3111 lower = 0.0;
3112 upper = 1000.0;
3113 range_reason = "the wall layer the model integrates over";
3114 break;
3115 }
3116
3117 if (global_sum[0] > 0.0 && (y_plus_mean < lower || y_plus_mean > upper)) {
3118 user->wall_yplus_excursions += 1;
3120 "Wall model (%s): first-cell y+ is %.2f, outside %s (%.0f to %.0f). "
3121 "The stress it reports is not one the law supports here. Sample %d "
3122 "of %d before this run is stopped.\n",
3124 (double)y_plus_mean, range_reason, (double)lower, (double)upper,
3125 (int)user->wall_yplus_excursions,
3126 (int)PICURV_WALL_YPLUS_GRACE_SAMPLES);
3127
3128 PetscCheck(user->wall_yplus_excursions < PICURV_WALL_YPLUS_GRACE_SAMPLES,
3129 PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
3130 "Wall model (%s): first-cell y+ has been outside %s (%.0f to %.0f) "
3131 "for %d consecutive samples, most recently %.2f. Refine or coarsen "
3132 "the wall-normal spacing so the first cell lands in range, or "
3133 "resolve the wall and disable the wall function. Stopping now "
3134 "rather than spending the run producing a stress the law cannot "
3135 "support.",
3137 range_reason, (double)lower, (double)upper,
3138 (int)user->wall_yplus_excursions, (double)y_plus_mean);
3139 } else {
3140 /* Consecutive, so a sample back in range clears the count. */
3141 user->wall_yplus_excursions = 0;
3142 }
3143 }
3144
3145 PetscFunctionReturn(0);
3146}
PetscErrorCode PicurvPhysicalTime(const SimCtx *simCtx, PetscReal solver_time, PetscReal *physical)
Convert a solver time to physical seconds, t * L_ref / U_ref.
Definition io.c:3177
const char * WallFunctionModelToString(WallFunctionModel model)
Returns the user-facing name of a wall-function model.
Definition logging.c:835
PetscErrorCode PicurvOpenDiagnosticsCsv(const SimCtx *simCtx, const char *filename, const char *header, FILE **file)
Opens a per-run diagnostics CSV in the run's analysis directory for appending.
Definition logging.c:3503
PetscReal wall_viscosity_sum
Sum of the effective wall eddy viscosity.
Definition variables.h:680
PetscInt wall_yplus_excursions
Consecutive diagnostic samples with the first cell outside the selected law's valid y+ range.
Definition variables.h:1159
PetscReal friction_velocity_sq
Sum of u_tau^2, for the RMS.
Definition variables.h:674
PetscReal ren
Definition variables.h:906
WallFunctionModel
Selects the wall model applied on WALL faces.
Definition variables.h:565
@ WALL_FUNCTION_LOG_LAW
Definition variables.h:567
@ WALL_FUNCTION_WERNER
Definition variables.h:568
PetscReal wall_distance_sum
Sum of the first-cell wall distance.
Definition variables.h:677
PetscReal friction_velocity_min
Smallest u_tau this rank corrected.
Definition variables.h:675
PetscReal friction_velocity_max
Largest u_tau this rank corrected.
Definition variables.h:676
PetscReal y_plus_sum
Sum of u_tau * y / nu over corrected cells.
Definition variables.h:678
PetscReal friction_velocity_sum
Sum of u_tau over corrected cells.
Definition variables.h:673
PetscInt cells
Number of cells this rank corrected.
Definition variables.h:681
PetscReal y_plus_max
Largest first-cell y+ this rank corrected.
Definition variables.h:679
PetscInt step
Definition variables.h:867
PetscReal ti
Definition variables.h:868
Near-wall statistics captured by one wall-model pass.
Definition variables.h:672
Here is the call graph for this function:
Here is the caller graph for this function:

◆ FinalizePostProjectionCellFields()

PetscErrorCode FinalizePostProjectionCellFields ( UserCtx *  user)

Finalizes cell-centered fields after the projection step.

This function completes the cell-centered state derived from the final, divergence-free Ucont produced by Projection. It fills non-periodic Ucat dummy faces, synchronizes periodic Ucat and P endpoints, resolves edges and corners, and refreshes the corresponding local vectors.

This function is fundamentally different from ApplyBoundaryConditions: it does NOT modify Ucont, reapply wall functions, or rerun the full physical boundary-condition workflow.

WORKFLOW:

  1. Refreshes local Ucat and any flow-dependent Ubcs targets.
  2. Fills non-periodic dummy faces and establishes periodic cell endpoints.
  3. Resolves edges/corners, restores exact periodic relationships, and refreshes local Ucat and P.
Parameters
userThe main UserCtx struct, containing all simulation state.
Returns
PetscErrorCode 0 on success.

Finalizes cell-centered fields after the projection step.

Full API contract is documented with the header declaration in include/Boundaries.h.

Definition at line 3155 of file Boundaries.c.

3156{
3157 PetscErrorCode ierr;
3158 const FieldId cell_fields[] = {FIELD_ID_UCAT, FIELD_ID_P};
3159
3160 PetscFunctionBeginUser;
3162
3163 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Finalizing post-projection cell-centered fields.\n");
3164
3165 // Ensure flow-dependent Ubcs handlers see the newly reconstructed Ucat.
3166 ierr = UpdateLocalGhosts(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3167 ierr = BoundarySystem_RefreshUbcs(user); CHKERRQ(ierr);
3168
3169 // Establish flat non-periodic faces and periodic endpoints before corners.
3170 ierr = UpdateDummyCells(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3171 ierr = UpdateDummyCells(user, FIELD_ID_P); CHKERRQ(ierr);
3172 ierr = SynchronizePeriodicCellFields(user, 2, cell_fields); CHKERRQ(ierr);
3173
3174 // Corner averaging can overwrite periodic endpoints, so restore them after.
3175 ierr = UpdateCornerNodes(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3176 ierr = UpdateCornerNodes(user, FIELD_ID_P); CHKERRQ(ierr);
3177 ierr = SynchronizePeriodicCellFields(user, 2, cell_fields); CHKERRQ(ierr);
3178
3179 // Synchronize explicitly because the periodic helper is a no-op when every
3180 // direction is non-periodic.
3181 ierr = UpdateLocalGhosts(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3182 ierr = UpdateLocalGhosts(user, FIELD_ID_P); CHKERRQ(ierr);
3183
3184 LOG_ALLOW(GLOBAL, LOG_DEBUG, "Post-projection cell-centered fields finalized.\n");
3186 PetscFunctionReturn(0);
3187}
PetscErrorCode UpdateDummyCells(UserCtx *user, FieldId field_id)
Implementation of UpdateDummyCells().
PetscErrorCode BoundarySystem_RefreshUbcs(UserCtx *user)
Internal helper implementation: BoundarySystem_RefreshUbcs().
PetscErrorCode UpdateCornerNodes(UserCtx *user, FieldId field_id)
Implementation of UpdateCornerNodes().
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ApplyBoundaryConditions()

PetscErrorCode ApplyBoundaryConditions ( UserCtx *  user)

Main boundary-condition orchestrator executed during solver timestepping.

This routine performs the full BC workflow for the current block, including dynamic boundary refresh, periodic transfer, dummy/corner updates, and optional wall-function corrections in the same order expected by the runtime solver. It may iterate boundary updates to enforce coupled boundary dependencies.

Parameters
userThe main UserCtx struct containing field vectors and boundary system state.
Returns
PetscErrorCode 0 on success.

Main boundary-condition orchestrator executed during solver timestepping.

Full API contract (arguments, ownership, side effects) is documented with the header declaration in include/Boundaries.h.

See also
ApplyBoundaryConditions()

Definition at line 3197 of file Boundaries.c.

3198{
3199 PetscErrorCode ierr;
3200 const FieldId staggered_fields[] = {FIELD_ID_UCONT};
3201 PetscFunctionBeginUser;
3203
3204 LOG_ALLOW(GLOBAL,LOG_TRACE,"Boundary Condition Application begins.\n");
3205
3206 // STEP 1: Main iteration loop for applying and converging non-periodic BCs.
3207 // The number of iterations (e.g., 3) allows information to propagate
3208 // between coupled boundaries, like an inlet and a conserving outlet.
3209 for (PetscInt iter = 0; iter < 3; iter++) {
3210 // (a) Execute the boundary system. This phase calculates fluxes across
3211 // the domain and then applies the physical logic for each non-periodic
3212 // handler, setting the `ubcs` (boundary value) array.
3213 ierr = BoundarySystem_ExecuteStep(user); CHKERRQ(ierr);
3214
3215 LOG_ALLOW(GLOBAL,LOG_VERBOSE,"Boundary Condition Setup Executed.\n");
3216
3217 // (b) Synchronize the updated ghost cells across all processors to ensure
3218 // all ucont values are current before updating the dummy cells.
3219 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); CHKERRQ(ierr);
3220
3221 // (c) Convert updated Contravariant velocities to Cartesian velocities.
3222 ierr = Contra2Cart(user); CHKERRQ(ierr);
3223
3224 // (d) Synchronize the updated Cartesian velocities across all processors
3225 // to ensure all ucat values are current before updating the dummy cells.
3226 ierr = UpdateLocalGhosts(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3227
3228 // (e) If Wall functions are enabled, apply them now to adjust near-wall velocities.
3229 if(user->simCtx->wallfunction){
3230 // Apply wall function adjustments to the boundary velocities.
3231 ierr = ApplyWallFunction(user); CHKERRQ(ierr);
3232
3233 // Synchronize the updated Cartesian velocities after wall function adjustments.
3234 ierr = UpdateLocalGhosts(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3235
3236 LOG_ALLOW(GLOBAL,LOG_VERBOSE,"Wall Function Applied at Walls.\n");
3237 }
3238
3239 // (f) Update the first layer of ghost cells for non-periodic faces using
3240 // the newly computed `ubcs` values.
3241 ierr = UpdateDummyCells(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3242 ierr = UpdateDummyCells(user, FIELD_ID_P); CHKERRQ(ierr);
3243
3244 LOG_ALLOW(GLOBAL,LOG_VERBOSE,"Dummy Cells/Ghost Cells Updated.\n");
3245
3246 // (g) Handle all periodic boundaries. This is a parallel direct copy
3247 // that sets the absolute constraints for the rest of the solve.
3248 // There is a Ghost update happening inside this function.
3249 ierr = ApplyPeriodicBCs(user); CHKERRQ(ierr);
3250
3251 // (h) Update the corner and edge ghost nodes. This routine calculates
3252 // values for corners/edges by averaging their neighbors, which have been
3253 // finalized in the steps above (both periodic and non-periodic).
3254 ierr = UpdateCornerNodes(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3255 ierr = UpdateCornerNodes(user, FIELD_ID_P); CHKERRQ(ierr);
3256
3257 // (i) Synchronize the updated edge and corner cells across all processors to ensure
3258 // consistency before the next iteration or finalization.
3259 ierr = UpdateLocalGhosts(user, FIELD_ID_P); CHKERRQ(ierr);
3260 ierr = UpdateLocalGhosts(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3261 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); CHKERRQ(ierr);
3262
3263 // (j) Ensure All the corners are synchronized with a well defined protocol in case of Periodic boundary conditions
3264 // To avoid race conditions.
3265 const FieldId all_fields[] = {FIELD_ID_UCAT, FIELD_ID_P, FIELD_ID_NVERT};
3266 ierr = SynchronizePeriodicCellFields(user, 3, all_fields); CHKERRQ(ierr);
3267
3268 }
3269
3270 // STEP 3: Final ghost node synchronization. This ensures all changes made
3271 // to the global vectors are reflected in the local ghost regions of all
3272 // processors, making the state fully consistent before the next solver stage.
3273 ierr = UpdateLocalGhosts(user, FIELD_ID_P); CHKERRQ(ierr);
3274 ierr = UpdateLocalGhosts(user, FIELD_ID_UCAT); CHKERRQ(ierr);
3275 ierr = SynchronizePeriodicStaggeredFields(user, 1, staggered_fields); CHKERRQ(ierr);
3276
3278 PetscFunctionReturn(0);
3279}
PetscErrorCode ApplyPeriodicBCs(UserCtx *user)
Internal helper implementation: ApplyPeriodicBCs().
PetscErrorCode ApplyWallFunction(UserCtx *user)
Internal helper implementation: ApplyWallFunction().
PetscErrorCode BoundarySystem_ExecuteStep(UserCtx *user)
Implementation of BoundarySystem_ExecuteStep().
PetscErrorCode Contra2Cart(UserCtx *user)
Reconstructs Cartesian velocity (Ucat) at cell centers from contravariant velocity (Ucont) defined on...
Definition setup.c:3300
Here is the call graph for this function:
Here is the caller graph for this function: