ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
erf_sbm Namespace Reference

Namespaces

 remap_detail
 

Classes

struct  AdvectionFacePolicyInput
 
struct  AdvectionFacePolicy
 
struct  SBMAMRStateView
 
struct  BulkProjection
 
class  SBMBulkProjection
 
struct  ConstraintTerm
 
struct  LinearConstraint
 
struct  ConstraintGroup
 
struct  ConstraintDescriptor
 
struct  FlattenedConstraintSet
 
struct  AttachedPropertySupportDescriptor
 
struct  ConstraintClosureChunk
 
struct  FixtureInitialStateCanonicality
 
struct  SpectralPopulationSpec
 
struct  LiquidProjectionSpec
 
struct  AttachedPropertyDescriptor
 
struct  PopulationLayout
 
struct  SBMLayoutSpec
 
struct  LayoutValidation
 
class  SBMLayout
 
struct  PopulationRemapView
 
struct  RouteDestination
 
struct  PacketRoutingPlan
 
struct  PacketApplicationCoreResult
 
struct  ReconstructionDeltaView
 
struct  IntegratedMomentsCoreResult
 
struct  PacketApplicationResult
 
struct  ReconstructionDelta
 
struct  IntegratedMoments
 
struct  RepresentationIdentity
 
class  SBMStateManager
 
class  SBMTransport
 
struct  SpectralGridSpec
 
struct  GridValidation
 
struct  EndpointTransform
 
class  SpectralGrid
 

Enumerations

enum class  AdvectionFaceKind : int { Periodic , ImpermeableWall , AdvectiveOutflow , PrescribedInflow }
 
enum class  AdvectionFaceAction : int {
  PeriodicSharedFace , ZeroNormalFlux , InteriorDonor , ExplicitSpectralInflow ,
  RejectInwardOutflow , RejectMissingInflowState , RejectIncompleteInflowState
}
 
enum class  PropertyKind { ExtensiveMass , NumberCarried , MassBoundedSubset }
 
enum class  PropertyRemapPolicy { CarrierBinConservative , Independent }
 
enum class  SupportRequirement { None , PositiveMass , PositiveNumber }
 
enum class  PopulationPhase { Liquid , Ice , Aerosol , Generic }
 
enum class  HostWritePath {
  Advection , Diffusion , Source , Positivity ,
  Microphysics , Wall , Boundary
}
 
enum class  RemapStatus {
  Ok , ZeroWaterResidual , BelowSupportedGrid , Overflow ,
  NumericalUnderflow , Invalid
}
 
enum class  ReconstructionStatus { Populated , Empty , Invalid }
 
enum class  CoordinateKind { Mass , Radius }
 
enum class  MomentMode { OneMoment , TwoMoment }
 

Functions

constexpr AdvectionFacePolicy make_advection_face_policy (const AdvectionFacePolicyInput &input) noexcept
 
bool RestrictMappedSpectrum (const SBMLayout &layout, const SBMAMRStateView &fine, const SBMAMRStateView &coarse, const amrex::IntVect &ratio, const int coarse_level, MultiFab &coarse_candidate, std::string &diagnostic)
 
bool ProlongCarrierRelativeSpectrum (const SBMLayout &layout, const SBMAMRStateView &coarse, const SBMAMRStateView &fine_target, const amrex::Geometry &coarse_geometry, const amrex::Geometry &fine_geometry, const amrex::IntVect &ratio, const int fine_level, MultiFab &fine_candidate, std::string &diagnostic)
 
bool RestrictMappedSpectrum (const SBMLayout &layout, const SBMAMRStateView &fine, const SBMAMRStateView &coarse, const amrex::IntVect &ratio, int coarse_level, amrex::MultiFab &coarse_candidate, std::string &diagnostic)
 
bool ProlongCarrierRelativeSpectrum (const SBMLayout &layout, const SBMAMRStateView &coarse, const SBMAMRStateView &fine_target, const amrex::Geometry &coarse_geometry, const amrex::Geometry &fine_geometry, const amrex::IntVect &ratio, int fine_level, amrex::MultiFab &fine_candidate, std::string &diagnostic)
 
std::string canonical_real (const amrex::Real value, const bool allow_unbounded_nan=false)
 
std::vector< ConstraintGroup > make_constraint_groups (const SBMLayout &layout)
 
FlattenedConstraintSet flatten_constraint_groups (const std::vector< ConstraintGroup > &groups, const int state_components)
 
FlattenedConstraintSet make_constraint_descriptors (const SBMLayout &layout)
 
std::vector< AttachedPropertySupportDescriptor > make_attached_property_support_descriptors (const SBMLayout &layout)
 
std::vector< ConstraintClosureChunk > make_constraint_closure_chunks (const SBMLayout &layout, const int max_groups)
 
EndpointTransform transform_two_moment (const amrex::Real C, const amrex::Real M, const amrex::Real lower, const amrex::Real upper)
 
std::pair< amrex::Real, amrex::Real > inverse_two_moment (const amrex::Real L, const amrex::Real H, const amrex::Real lower, const amrex::Real upper)
 
bool property_support_is_admissible (const amrex::Real property, const amrex::Real carrier_number, const amrex::Real lower, const amrex::Real upper, const amrex::Real tolerance) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real evaluate_constraint_descriptor (const ConstraintDescriptor &constraint, const ConstraintTerm *terms, const amrex::Real *state) noexcept
 
FixtureInitialStateCanonicality validate_fixture_initial_state_canonicality (const SBMLayout &layout, const std::vector< amrex::Real > &state) noexcept
 
const char * host_write_path_name (const HostWritePath path) noexcept
 
bool host_write_allowed (const bool sbm_active, const int component) noexcept
 
bool host_write_range_allowed (const bool sbm_active, const int start_component, const int num_components) noexcept
 
void require_host_write_allowed (const bool sbm_active, const int component, const HostWritePath path)
 
void require_host_write_range_allowed (const bool sbm_active, const int start_component, const int num_components, const HostWritePath path)
 
PopulationRemapView population_remap_view (const SBMLayout &layout, const int population_id) noexcept
 
PacketApplicationResult apply_packet_routing (const SBMLayout &layout, const PacketRoutingPlan &plan, const std::vector< amrex::Real > &property_packet_amounts, std::vector< amrex::Real > &candidate_state)
 
ReconstructionStatus reconstruct_bin (const SBMLayout &layout, const int population_id, const int bin, const std::vector< amrex::Real > &persisted_state, ReconstructionDelta &reconstruction)
 
bool integrate_interval (const ReconstructionDelta &reconstruction, const amrex::Real lower, const amrex::Real upper, const bool include_upper, IntegratedMoments &integral)
 
bool project_reconstruction_bin (const SBMLayout &layout, const ReconstructionDelta &reconstruction, std::vector< amrex::Real > &candidate_state)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool valid_population_remap_context (const PopulationRemapView &population) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool valid_population_remap_view (const PopulationRemapView &population) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool same_population_context (const PopulationRemapView &left, const PopulationRemapView &right) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE PacketRoutingPlan plan_packet_routing (const PopulationRemapView population, const amrex::Real packet_number, const amrex::Real particle_mass) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE PacketApplicationCoreResult apply_packet_routing_core (const PopulationRemapView population, const PacketRoutingPlan plan, const amrex::Real *property_packet_amounts, const int property_count, amrex::Real *residual_properties, const int residual_capacity, amrex::Real *candidate_state, const int state_count) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE ReconstructionStatus reconstruct_bin_core (const PopulationRemapView population, const int bin, const amrex::Real *persisted_state, const int state_count, amrex::Real *property_per_particle, const int property_capacity, ReconstructionDeltaView &reconstruction) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool integrate_interval_core (const ReconstructionDeltaView reconstruction, const amrex::Real lower, const amrex::Real upper, const bool include_upper, amrex::Real *attached_properties, const int property_capacity, IntegratedMomentsCoreResult &integral) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool project_reconstruction_bin_core (const PopulationRemapView population, const ReconstructionDeltaView reconstruction, amrex::Real *candidate_state, const int state_count) noexcept
 
constexpr RepresentationIdentity representation_identity (const MomentMode mode) noexcept
 
std::string restart_schema (const SBMLayout &layout)
 
bool restart_schema_matches (const SBMLayout &layout, const std::string &persisted)
 
bool authoritative_state_admissible (const amrex::MultiFab &spectrum, const SBMLayout &layout, const int level, std::string *diagnostic)
 
bool restart_projection_matches (const amrex::MultiFab &spectrum, const amrex::MultiFab &persisted_core, const SBMBulkProjection &projection, const int qc_component, const int qr_component, const amrex::Real tolerance_scale)
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void copy_host_slow_component (const amrex::Array4< amrex::Real > &current, const amrex::Array4< const amrex::Real > &predictor, const int i, const int j, const int k, const int component, const bool sbm_active, const int qc_component, const int qr_component) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool try_two_moment_to_endpoints (const amrex::Real C, const amrex::Real M, const amrex::Real lower, const amrex::Real upper, EndpointTransform &result) noexcept
 
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool try_endpoints_to_two_moment (const amrex::Real L, const amrex::Real H, const amrex::Real lower, const amrex::Real upper, amrex::Real &C, amrex::Real &M) noexcept
 

Enumeration Type Documentation

◆ AdvectionFaceAction

enum erf_sbm::AdvectionFaceAction : int
strong

Result of applying the M3 spectral state policy at one oriented face.

Enumerator
PeriodicSharedFace 
ZeroNormalFlux 
InteriorDonor 
ExplicitSpectralInflow 
RejectInwardOutflow 
RejectMissingInflowState 
RejectIncompleteInflowState 

◆ AdvectionFaceKind

enum erf_sbm::AdvectionFaceKind : int
strong

Physical face categories understood by the bounded M3 advection contract.

Enumerator
Periodic 
ImpermeableWall 
AdvectiveOutflow 
PrescribedInflow 

◆ CoordinateKind

Enumerator
Mass 
Radius 

◆ HostWritePath

Enumerator
Advection 
Diffusion 
Source 
Positivity 
Microphysics 
Wall 
Boundary 
10  {
11  Advection,
12  Diffusion,
13  Source,
14  Positivity,
16  Wall,
17  Boundary
18 };
Base class for microphysics interface.
Definition: ERF_Microphysics.H:17
amrex::GpuArray< FaceWall, 2 *AMREX_SPACEDIM > Boundary
Definition: ERF_WallThermodynamics.H:70

◆ MomentMode

enum erf_sbm::MomentMode
strong
Enumerator
OneMoment 
TwoMoment 

◆ PopulationPhase

◆ PropertyKind

enum erf_sbm::PropertyKind
strong

Semantic class of an attached carrier-bin extensive quantity.

ExtensiveMass is nonnegative additional mass and is not part of the water projection. NumberCarried is a nonnegative count-like quantity. A MassBoundedSubset is nonnegative and may be constrained by its carrier mass when that future physics is implemented.

Enumerator
ExtensiveMass 
NumberCarried 
MassBoundedSubset 

◆ PropertyRemapPolicy

P1/P2 remap contract; independent remapping is reserved for future work.

Enumerator
CarrierBinConservative 
Independent 

◆ ReconstructionStatus

◆ RemapStatus

enum erf_sbm::RemapStatus
strong
Enumerator
Ok 
ZeroWaterResidual 
BelowSupportedGrid 
Overflow 
NumericalUnderflow 
Invalid 

◆ SupportRequirement

Enumerator
None 
PositiveMass 
PositiveNumber 

Function Documentation

◆ apply_packet_routing()

PacketApplicationResult erf_sbm::apply_packet_routing ( const SBMLayout &  layout,
const PacketRoutingPlan &  plan,
const std::vector< amrex::Real > &  property_packet_amounts,
std::vector< amrex::Real > &  candidate_state 
)
43 {
44  PacketApplicationResult result;
45  result.status = plan.status;
46  if (plan.status != RemapStatus::Ok && plan.status != RemapStatus::ZeroWaterResidual) {
47  return result;
48  }
49  if (&property_packet_amounts == &candidate_state) {
50  result.status = RemapStatus::Invalid;
51  return result;
52  }
53  const auto population = population_remap_view(layout, plan.population.population_id);
54  if (population.property_count >= 0) {
55  result.residual_properties.resize(static_cast<std::size_t>(population.property_count),
56  amrex::Real(0.0));
57  }
58  const auto core = apply_packet_routing_core(
59  population, plan,
60  property_packet_amounts.data(), static_cast<int>(property_packet_amounts.size()),
61  result.residual_properties.data(), static_cast<int>(result.residual_properties.size()),
62  candidate_state.data(), static_cast<int>(candidate_state.size()));
63  result.status = core.status;
64  result.residual_number = core.residual_number;
65  result.residual_water_mass = core.residual_water_mass;
66  result.roundoff_water_mass_correction = core.roundoff_water_mass_correction;
67  result.normalized_roundoff = core.normalized_roundoff;
68  return result;
69 }
amrex::Real Real
Definition: ERF_ShocInterface.H:19
PopulationRemapView population_remap_view(const SBMLayout &layout, const int population_id) noexcept
Definition: ERF_SBMRemapping.cpp:8
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE PacketApplicationCoreResult apply_packet_routing_core(const PopulationRemapView population, const PacketRoutingPlan plan, const amrex::Real *property_packet_amounts, const int property_count, amrex::Real *residual_properties, const int residual_capacity, amrex::Real *candidate_state, const int state_count) noexcept
Definition: ERF_SBMRemapping.H:640
Here is the call graph for this function:

◆ apply_packet_routing_core()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE PacketApplicationCoreResult erf_sbm::apply_packet_routing_core ( const PopulationRemapView  population,
const PacketRoutingPlan  plan,
const amrex::Real *  property_packet_amounts,
const int  property_count,
amrex::Real *  residual_properties,
const int  residual_capacity,
amrex::Real *  candidate_state,
const int  state_count 
)
noexcept

Allocation-free packet application. The first pass validates the complete plan, residual outputs, and every candidate increment. The second pass commits only after all failure paths have been ruled out. Input/output and property/state storage supplied by the caller must not overlap.

645 {
646  PacketApplicationCoreResult result;
647  result.status = plan.status;
648  if (plan.status != RemapStatus::Ok && plan.status != RemapStatus::ZeroWaterResidual) {
649  return result;
650  }
651  if (!valid_population_remap_context(population) ||
652  !same_population_context(population, plan.population) ||
653  !amrex::Math::isfinite(plan.packet_number) ||
654  !amrex::Math::isfinite(plan.packet_particle_mass) ||
655  plan.packet_number < amrex::Real(0.0) || plan.packet_particle_mass < amrex::Real(0.0) ||
656  property_count != population.property_count ||
657  (property_count > 0 && (property_packet_amounts == nullptr || residual_properties == nullptr)) ||
658  residual_capacity < property_count || candidate_state == nullptr ||
659  state_count != population.state_components || plan.destination_count < 0 ||
660  plan.destination_count > 2 || !amrex::Math::isfinite(plan.residual_number_weight) ||
661  plan.residual_number_weight < amrex::Real(0.0) ||
662  plan.residual_number_weight > amrex::Real(1.0)) {
663  result.status = RemapStatus::Invalid;
664  return result;
665  }
666  if (!remap_detail::route_plan_is_consistent(population, plan)) {
667  result.status = RemapStatus::Invalid;
668  return result;
669  }
670  for (int property = 0; property < property_count; ++property) {
671  if (!remap_detail::finite_nonnegative(property_packet_amounts[property])) {
672  result.status = RemapStatus::Invalid;
673  return result;
674  }
675  }
676  if (plan.packet_number == amrex::Real(0.0)) {
677  for (int property = 0; property < property_count; ++property) {
678  if (property_packet_amounts[property] != amrex::Real(0.0)) {
679  result.status = RemapStatus::Invalid;
680  return result;
681  }
682  }
683  }
684  amrex::Real weight_sum = plan.residual_number_weight;
685  for (int destination = 0; destination < plan.destination_count; ++destination) {
686  const auto& route = plan.destinations[destination];
687  if (route.bin < 0 || route.bin >= population.nbins ||
688  !amrex::Math::isfinite(route.number_weight) || route.number_weight < amrex::Real(0.0) ||
689  route.number_weight > amrex::Real(1.0) ||
690  !remap_detail::finite_nonnegative(route.particle_mass)) {
691  result.status = RemapStatus::Invalid;
692  return result;
693  }
694  for (int previous = 0; previous < destination; ++previous) {
695  if (plan.destinations[previous].bin == route.bin) {
696  result.status = RemapStatus::Invalid;
697  return result;
698  }
699  }
700  weight_sum += route.number_weight;
701  }
702  if (plan.packet_number > amrex::Real(0.0)) {
703  const amrex::Real weight_tolerance = amrex::Real(16.0) *
705  if (!amrex::Math::isfinite(weight_sum) ||
706  std::abs(weight_sum - amrex::Real(1.0)) > weight_tolerance ||
707  ((plan.status == RemapStatus::ZeroWaterResidual) !=
708  (plan.residual_number_weight > amrex::Real(0.0)))) {
709  result.status = RemapStatus::Invalid;
710  return result;
711  }
712  } else if (plan.destination_count != 0 ||
713  plan.residual_number_weight != amrex::Real(0.0)) {
714  result.status = RemapStatus::Invalid;
715  return result;
716  }
717 
718  amrex::Real residual_number = amrex::Real(0.0);
719  auto product_status = remap_detail::checked_product(
720  plan.packet_number, plan.residual_number_weight, residual_number);
721  if (product_status != remap_detail::ProductStatus::Ok) {
722  result.status = remap_detail::remap_product_status(product_status);
723  return result;
724  }
725  amrex::Real roundoff_water_mass_correction = amrex::Real(0.0);
726  // Validate all outputs and candidate increments before changing either.
727  for (int property = 0; property < property_count; ++property) {
728  amrex::Real residual = amrex::Real(0.0);
729  product_status = remap_detail::checked_product(
730  property_packet_amounts[property], plan.residual_number_weight, residual);
731  if (product_status != remap_detail::ProductStatus::Ok) {
732  result.status = remap_detail::remap_product_status(product_status);
733  return result;
734  }
735  }
736  for (int destination = 0; destination < plan.destination_count; ++destination) {
737  const auto& route = plan.destinations[destination];
738  amrex::Real number_increment = amrex::Real(0.0);
739  amrex::Real mass_increment = amrex::Real(0.0);
740  const amrex::Real deposited_mass = route.particle_mass;
741  product_status = remap_detail::checked_product(
742  plan.packet_number, route.number_weight, number_increment);
743  if (product_status == remap_detail::ProductStatus::Ok) {
744  product_status = remap_detail::checked_product(
745  number_increment, deposited_mass, mass_increment);
746  }
747  if (product_status != remap_detail::ProductStatus::Ok) {
748  result.status = remap_detail::remap_product_status(product_status);
749  return result;
750  }
751  if (population.moment_mode == MomentMode::TwoMoment && plan.normalized_roundoff) {
752  const amrex::Real mass_delta =
753  route.particle_mass - plan.packet_particle_mass;
754  if (!amrex::Math::isfinite(mass_delta) ||
755  mass_delta == amrex::Real(0.0)) {
756  result.status = RemapStatus::Invalid;
757  return result;
758  }
759  const amrex::Real mass_delta_magnitude = mass_delta < amrex::Real(0.0) ?
760  -mass_delta : mass_delta;
761  amrex::Real correction_magnitude = amrex::Real(0.0);
762  product_status = remap_detail::checked_product(
763  plan.packet_number, mass_delta_magnitude, correction_magnitude);
764  if (product_status != remap_detail::ProductStatus::Ok) {
765  result.status = remap_detail::remap_product_status(product_status);
766  return result;
767  }
768  const amrex::Real correction = mass_delta < amrex::Real(0.0) ?
769  -correction_magnitude : correction_magnitude;
770  if (!amrex::Math::isfinite(correction) ||
771  !amrex::Math::isfinite(roundoff_water_mass_correction + correction)) {
772  result.status = RemapStatus::Invalid;
773  return result;
774  }
775  roundoff_water_mass_correction += correction;
776  }
777  const int mass_component = population.mass_offset + route.bin;
778  const amrex::Real old_mass = candidate_state[mass_component];
779  if (!remap_detail::finite_nonnegative(old_mass) ||
780  !remap_detail::finite_nonnegative(old_mass + mass_increment)) {
781  result.status = RemapStatus::Invalid;
782  return result;
783  }
784  const amrex::Real next_mass = old_mass + mass_increment;
785  amrex::Real next_number = amrex::Real(0.0);
786  if (population.number_offset >= 0) {
787  const int number_component = population.number_offset + route.bin;
788  const amrex::Real old_number = candidate_state[number_component];
789  if (!remap_detail::finite_nonnegative(old_number)) {
790  result.status = RemapStatus::Invalid;
791  return result;
792  }
793  next_number = old_number + number_increment;
794  if (!remap_detail::finite_nonnegative(next_number)) {
795  result.status = RemapStatus::Invalid;
796  return result;
797  }
798  if (next_number == amrex::Real(0.0)) {
799  if (next_mass != amrex::Real(0.0)) {
800  result.status = RemapStatus::Invalid;
801  return result;
802  }
803  } else {
804  amrex::Real mean = amrex::Real(0.0);
805  const auto quotient_status = remap_detail::checked_quotient(
806  next_mass, next_number, mean);
807  if (quotient_status != remap_detail::QuotientStatus::Ok) {
808  result.status = remap_detail::remap_quotient_status(quotient_status);
809  return result;
810  }
812  population, route.bin, next_number, next_mass)) {
813  result.status = RemapStatus::Invalid;
814  return result;
815  }
816  }
817  } else {
818  const auto quotient_status = remap_detail::checked_quotient(
819  next_mass, population.pivots[route.bin], next_number);
820  if (quotient_status != remap_detail::QuotientStatus::Ok) {
821  result.status = remap_detail::remap_quotient_status(quotient_status);
822  return result;
823  }
824  }
825  for (int property = 0; property < property_count; ++property) {
826  amrex::Real increment = amrex::Real(0.0);
827  const int component = population.property_component_offsets[property] + route.bin;
828  const amrex::Real old_value = candidate_state[component];
829  product_status = remap_detail::checked_product(
830  property_packet_amounts[property], route.number_weight, increment);
831  if (product_status != remap_detail::ProductStatus::Ok) {
832  result.status = remap_detail::remap_product_status(product_status);
833  return result;
834  }
835  if (!remap_detail::finite_nonnegative(old_value)) {
836  result.status = RemapStatus::Invalid;
837  return result;
838  }
839  const amrex::Real next_value = old_value + increment;
840  if (!remap_detail::finite_nonnegative(next_value)) {
841  result.status = RemapStatus::Invalid;
842  return result;
843  }
844  if (next_number == amrex::Real(0.0)) {
845  if (next_value != amrex::Real(0.0)) {
846  result.status = RemapStatus::Invalid;
847  return result;
848  }
849  } else {
850  amrex::Real per_particle = amrex::Real(0.0);
851  const auto quotient_status = remap_detail::checked_quotient(
852  next_value, next_number, per_particle);
853  if (quotient_status != remap_detail::QuotientStatus::Ok) {
854  result.status = remap_detail::remap_quotient_status(quotient_status);
855  return result;
856  }
857  }
858  }
859  }
860 
861  for (int destination = 0; destination < plan.destination_count; ++destination) {
862  const auto& route = plan.destinations[destination];
863  const amrex::Real number_increment = plan.packet_number * route.number_weight;
864  const amrex::Real deposited_mass = route.particle_mass;
865  candidate_state[population.mass_offset + route.bin] += number_increment * deposited_mass;
866  if (population.number_offset >= 0) {
867  candidate_state[population.number_offset + route.bin] += number_increment;
868  }
869  for (int property = 0; property < property_count; ++property) {
870  candidate_state[population.property_component_offsets[property] + route.bin] +=
871  property_packet_amounts[property] * route.number_weight;
872  }
873  }
874  for (int property = 0; property < property_count; ++property) {
875  residual_properties[property] =
876  property_packet_amounts[property] * plan.residual_number_weight;
877  }
878  result.residual_number = residual_number;
879  result.residual_water_mass = amrex::Real(0.0);
880  result.roundoff_water_mass_correction = roundoff_water_mass_correction;
881  result.normalized_roundoff = plan.normalized_roundoff;
882  return result;
883 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool canonical_two_moment_moments(const PopulationRemapView &population, const int bin, const amrex::Real number, const amrex::Real mass) noexcept
Definition: ERF_SBMRemapping.H:345
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE ProductStatus checked_product(const amrex::Real left, const amrex::Real right, amrex::Real &result) noexcept
Definition: ERF_SBMRemapping.H:171
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool finite_nonnegative(const amrex::Real value) noexcept
Definition: ERF_SBMRemapping.H:165
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE RemapStatus remap_product_status(const ProductStatus status) noexcept
Definition: ERF_SBMRemapping.H:205
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool route_plan_is_consistent(const PopulationRemapView &population, const PacketRoutingPlan &plan) noexcept
Definition: ERF_SBMRemapping.H:235
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE QuotientStatus checked_quotient(const amrex::Real numerator, const amrex::Real denominator, amrex::Real &result) noexcept
Definition: ERF_SBMRemapping.H:183
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE RemapStatus remap_quotient_status(const QuotientStatus status) noexcept
Definition: ERF_SBMRemapping.H:212
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool same_population_context(const PopulationRemapView &left, const PopulationRemapView &right) noexcept
Definition: ERF_SBMRemapping.H:127
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool valid_population_remap_context(const PopulationRemapView &population) noexcept
Definition: ERF_SBMRemapping.H:53
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12

Referenced by apply_packet_routing().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ authoritative_state_admissible()

bool erf_sbm::authoritative_state_admissible ( const amrex::MultiFab &  spectrum,
const SBMLayout &  layout,
int  level,
std::string *  diagnostic = nullptr 
)

Validate every valid-cell value against the layout's runtime constraints.

43 {
44  auto reject = [diagnostic](std::string message) {
45  if (diagnostic) *diagnostic = std::move(message);
46  return false;
47  };
48  if (diagnostic) diagnostic->clear();
49  if (level < 0) return reject("SBM authoritative restart state has an invalid level");
50  if (spectrum.nComp() != layout.ncomp()) {
51  return reject("SBM authoritative restart state component count does not match its layout");
52  }
53 
54  const auto groups = make_constraint_groups(layout);
55  std::vector<PopulationRemapView> remap_views;
56  remap_views.reserve(layout.populations().size());
57  for (const auto& population : layout.populations()) {
58  remap_views.push_back(population_remap_view(layout, population.population_id));
59  }
60  std::vector<amrex::Real> state(static_cast<std::size_t>(layout.ncomp()));
61  const int precision = std::numeric_limits<amrex::Real>::max_digits10;
62 
63  for (amrex::MFIter mfi(spectrum); mfi.isValid(); ++mfi) {
64  const amrex::FArrayBox& source_fab = spectrum[mfi];
65  const amrex::FArrayBox* host_source = &source_fab;
66  std::unique_ptr<amrex::FArrayBox> host_fab;
67 #ifdef AMREX_USE_GPU
68  if (source_fab.arena()->isManaged() || source_fab.arena()->isDevice()) {
69  host_fab = std::make_unique<amrex::FArrayBox>(
70  source_fab.box(), source_fab.nComp(), amrex::The_Pinned_Arena());
71  amrex::Gpu::dtoh_memcpy_async(
72  host_fab->dataPtr(), source_fab.dataPtr(),
73  static_cast<std::size_t>(source_fab.size()) * sizeof(amrex::Real));
74  amrex::Gpu::streamSynchronize();
75  host_source = host_fab.get();
76  }
77 #endif
78  const auto values = host_source->const_array();
79  for (amrex::BoxIterator iterator(mfi.validbox()); iterator.ok(); ++iterator) {
80  const amrex::IntVect cell = iterator();
81  for (int component = 0; component < layout.ncomp(); ++component) {
82  const amrex::Real value = values(cell, component);
83  if (!std::isfinite(value)) {
84  std::ostringstream message;
85  message << "SBM authoritative restart state is inadmissible"
86  << ": level=" << level << ", cell=(";
87  for (int direction = 0; direction < AMREX_SPACEDIM; ++direction) {
88  if (direction != 0) message << ',';
89  message << cell[direction];
90  }
91  message << "), component=" << component
92  << ", constraint=finite, value="
93  << std::setprecision(precision) << value;
94  const auto group = std::find_if(groups.begin(), groups.end(),
95  [component](const ConstraintGroup& candidate) {
96  return candidate.contains(component);
97  });
98  if (group != groups.end()) {
99  message << ", population=" << group->population_id
100  << " (" << group->semantic_id << ")"
101  << ", bin=" << group->bin;
102  }
103  return reject(message.str());
104  }
105  state[static_cast<std::size_t>(component)] = value;
106  }
107 
108  for (std::size_t population_index = 0;
109  population_index < remap_views.size(); ++population_index) {
110  const auto& view = remap_views[population_index];
111  if (view.moment_mode != MomentMode::TwoMoment) continue;
112  for (int bin = 0; bin < view.nbins; ++bin) {
114  view, bin, state.data(), static_cast<int>(state.size()))) continue;
115  std::ostringstream message;
116  message << "SBM authoritative restart state is inadmissible"
117  << ": level=" << level << ", cell=(";
118  for (int direction = 0; direction < AMREX_SPACEDIM; ++direction) {
119  if (direction != 0) message << ',';
120  message << cell[direction];
121  }
122  message << "), population=" << view.population_id
123  << ", bin=" << bin
124  << ", constraint=canonical-two-moment-bin-state";
125  return reject(message.str());
126  }
127  }
128 
129  for (const auto& group : groups) {
130  amrex::Real margin = amrex::Real(0.0);
131  std::string failed_constraint;
132  if (group.admissible(state, &margin, &failed_constraint)) continue;
133 
134  std::ostringstream message;
135  message << "SBM authoritative restart state is inadmissible"
136  << ": level=" << level << ", cell=(";
137  for (int direction = 0; direction < AMREX_SPACEDIM; ++direction) {
138  if (direction != 0) message << ',';
139  message << cell[direction];
140  }
141  message << "), population=" << group.population_id
142  << " (" << group.semantic_id << ")"
143  << ", bin=" << group.bin
144  << ", constraint=" << failed_constraint
145  << ", margin=" << std::setprecision(precision) << margin
146  << ", components={";
147  for (std::size_t member = 0; member < group.members.size(); ++member) {
148  if (member != 0) message << ',';
149  const int component = group.members[member];
150  message << component << ':'
151  << std::setprecision(precision)
152  << state[static_cast<std::size_t>(component)];
153  }
154  message << '}';
155  return reject(message.str());
156  }
157 
158  for (std::size_t population_index = 0;
159  population_index < remap_views.size(); ++population_index) {
160  const auto& view = remap_views[population_index];
161  if (view.moment_mode != MomentMode::OneMoment) continue;
162  for (int bin = 0; bin < view.nbins; ++bin) {
164  view, bin, state.data(), static_cast<int>(state.size()))) continue;
165  std::ostringstream message;
166  message << "SBM authoritative restart state is inadmissible"
167  << ": level=" << level << ", cell=(";
168  for (int direction = 0; direction < AMREX_SPACEDIM; ++direction) {
169  if (direction != 0) message << ',';
170  message << cell[direction];
171  }
172  message << "), population=" << view.population_id
173  << ", bin=" << bin
174  << ", constraint=canonical-one-moment-bin-state";
175  return reject(message.str());
176  }
177  }
178  }
179  }
180  return true;
181 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool canonical_two_moment_bin_state(const PopulationRemapView &population, const int bin, const amrex::Real *state, const int state_count) noexcept
Definition: ERF_SBMRemapping.H:407
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool canonical_persisted_bin_state(const PopulationRemapView &population, const int bin, const amrex::Real *state, const int state_count) noexcept
Definition: ERF_SBMRemapping.H:475
std::vector< ConstraintGroup > make_constraint_groups(const SBMLayout &layout)
Definition: ERF_SBMConstraintGroups.cpp:77

Referenced by erf_sbm::SBMTransport::advance_stage(), ProlongCarrierRelativeSpectrum(), ERF::ReadCheckpointFile(), and RestrictMappedSpectrum().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ canonical_real()

std::string erf_sbm::canonical_real ( const amrex::Real  value,
const bool  allow_unbounded_nan = false 
)
inline
20 {
21  if (allow_unbounded_nan && std::isnan(value)) return "unbounded";
22  std::ostringstream out;
23  out.imbue(std::locale::classic());
24  out.setf(std::ios_base::scientific, std::ios_base::floatfield);
25  out << std::setprecision(std::numeric_limits<amrex::Real>::max_digits10) << value;
26  return out.str();
27 }

Referenced by erf_sbm::SpectralGrid::identity(), and erf_sbm::SBMLayout::SBMLayout().

Here is the caller graph for this function:

◆ copy_host_slow_component()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void erf_sbm::copy_host_slow_component ( const amrex::Array4< amrex::Real > &  current,
const amrex::Array4< const amrex::Real > &  predictor,
const int  i,
const int  j,
const int  k,
const int  component,
const bool  sbm_active,
const int  qc_component,
const int  qr_component 
)
noexcept

Perform ERF's predictor-to-current copy for one slow state component.

20 {
21  const bool sbm_projected_liquid =
22  sbm_active &&
23  (component == qc_component || component == qr_component);
24  if (!sbm_projected_liquid) {
25  current(i, j, k, component) = predictor(i, j, k, component);
26  }
27 }

Referenced by erf_slow_rhs_post().

Here is the caller graph for this function:

◆ evaluate_constraint_descriptor()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real erf_sbm::evaluate_constraint_descriptor ( const ConstraintDescriptor &  constraint,
const ConstraintTerm *  terms,
const amrex::Real *  state 
)
noexcept

Evaluate a validated flattened constraint in host or device code.

73 {
74  amrex::Real result = amrex::Real(0.0);
75  for (int i = 0; i < constraint.term_count; ++i) {
76  const auto& term = terms[constraint.term_offset + i];
77  result = std::fma(term.coefficient, state[term.component], result);
78  }
79  return result;
80 }

◆ flatten_constraint_groups()

FlattenedConstraintSet erf_sbm::flatten_constraint_groups ( const std::vector< ConstraintGroup > &  groups,
int  state_components 
)

Flatten arbitrary semantic groups through the production sparse format.

169 {
170  if (state_components < 0) {
171  throw std::invalid_argument("SBM production constraints require a nonnegative state size");
172  }
173  FlattenedConstraintSet flattened;
174  for (std::size_t gi = 0; gi < groups.size(); ++gi) {
175  const auto& group = groups[gi];
176  for (std::size_t ci = 0; ci < group.constraints.size(); ++ci) {
177  const auto& constraint = group.constraints[ci];
178  if (constraint.terms.empty() ||
179  constraint.terms.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()) ||
180  flattened.terms.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()) -
181  constraint.terms.size()) {
182  throw std::invalid_argument(
183  "SBM production constraints require a nonempty representable sparse term range: " +
184  constraint.semantic_id);
185  }
186  ConstraintDescriptor descriptor;
187  descriptor.group_index = static_cast<int>(gi);
188  descriptor.population_id = group.population_id;
189  descriptor.bin = group.bin;
190  descriptor.constraint_index = static_cast<int>(ci);
191  descriptor.term_offset = static_cast<int>(flattened.terms.size());
192  descriptor.term_count = static_cast<int>(constraint.terms.size());
193  for (const auto& term : constraint.terms) {
194  if (term.component < 0 || term.component >= state_components ||
195  !finite(term.coefficient)) {
196  throw std::invalid_argument(
197  "SBM production constraint has an invalid sparse term: " +
198  constraint.semantic_id);
199  }
200  flattened.terms.push_back(term);
201  }
202  flattened.constraints.push_back(descriptor);
203  }
204  }
205  return flattened;
206 }
DiagnosticDescriptor descriptor
Definition: ERF_Plotfile2DCatalog.cpp:108
bool finite(amrex::Real value) noexcept
Definition: ERF_CloudChamber.H:342
real(c_double), private ci
Definition: ERF_module_mp_morr_two_moment.F90:203

Referenced by make_constraint_descriptors().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ host_write_allowed()

bool erf_sbm::host_write_allowed ( bool  sbm_active,
int  component 
)
noexcept

The native host retains qv; SBM exclusively owns the compact liquid lanes.

24 {
25  return host_write_range_allowed(sbm_active, component, 1);
26 }
bool host_write_range_allowed(const bool sbm_active, const int start_component, const int num_components) noexcept
Definition: ERF_SBMOwnership.cpp:28
Here is the call graph for this function:

◆ host_write_path_name()

const char * erf_sbm::host_write_path_name ( const HostWritePath  path)
noexcept
10 {
11  switch (path) {
12  case HostWritePath::Advection: return "advection";
13  case HostWritePath::Diffusion: return "diffusion";
14  case HostWritePath::Source: return "source";
15  case HostWritePath::Positivity: return "positivity";
16  case HostWritePath::Microphysics: return "microphysics";
17  case HostWritePath::Wall: return "wall";
18  case HostWritePath::Boundary: return "boundary";
19  }
20  return "unknown";
21 }

Referenced by require_host_write_range_allowed().

Here is the caller graph for this function:

◆ host_write_range_allowed()

bool erf_sbm::host_write_range_allowed ( bool  sbm_active,
int  start_component,
int  num_components 
)
noexcept

Check every component in a native writer's complete, non-empty write range.

30 {
31  if (start_component < 0 || num_components <= 0 ||
32  num_components > std::numeric_limits<int>::max() - start_component) {
33  return false;
34  }
35  if (!sbm_active) return true;
36  const int end_component = start_component + num_components;
37  return !(start_component <= RhoQ2_comp && RhoQ2_comp < end_component) &&
38  !(start_component <= RhoQ3_comp && RhoQ3_comp < end_component);
39 }
#define RhoQ2_comp
Definition: ERF_IndexDefines.H:49
#define RhoQ3_comp
Definition: ERF_IndexDefines.H:50

Referenced by host_write_allowed(), and require_host_write_range_allowed().

Here is the caller graph for this function:

◆ integrate_interval()

bool erf_sbm::integrate_interval ( const ReconstructionDelta &  reconstruction,
const amrex::Real  lower,
const amrex::Real  upper,
const bool  include_upper,
IntegratedMoments &  integral 
)
101 {
102  std::vector<amrex::Real> properties(reconstruction.property_per_particle.size(),
103  amrex::Real(0.0));
104  ReconstructionDeltaView view;
105  view.bin = reconstruction.bin;
106  view.empty = reconstruction.empty;
107  view.normalized_roundoff = reconstruction.normalized_roundoff;
108  view.number = reconstruction.number;
109  view.particle_mass = reconstruction.particle_mass;
110  view.property_per_particle = reconstruction.property_per_particle.data();
111  view.property_count = static_cast<int>(reconstruction.property_per_particle.size());
112  IntegratedMomentsCoreResult core;
113  if (!integrate_interval_core(view, lower, upper, include_upper,
114  properties.data(), static_cast<int>(properties.size()), core)) {
115  return false;
116  }
117  IntegratedMoments next;
118  next.number = core.number;
119  next.water_mass = core.water_mass;
120  next.attached_properties = std::move(properties);
121  integral = std::move(next);
122  return true;
123 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool integrate_interval_core(const ReconstructionDeltaView reconstruction, const amrex::Real lower, const amrex::Real upper, const bool include_upper, amrex::Real *attached_properties, const int property_capacity, IntegratedMomentsCoreResult &integral) noexcept
Definition: ERF_SBMRemapping.H:990
Here is the call graph for this function:

◆ integrate_interval_core()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool erf_sbm::integrate_interval_core ( const ReconstructionDeltaView  reconstruction,
const amrex::Real  lower,
const amrex::Real  upper,
const bool  include_upper,
amrex::Real *  attached_properties,
const int  property_capacity,
IntegratedMomentsCoreResult &  integral 
)
noexcept

Integrate one reconstructed delta over [lower, upper), optionally including upper.

995 {
996  if (!amrex::Math::isfinite(lower) || !amrex::Math::isfinite(upper) || !(lower < upper) ||
997  !remap_detail::finite_nonnegative(reconstruction.number) ||
998  !remap_detail::finite_nonnegative(reconstruction.particle_mass) ||
999  reconstruction.property_count < 0 || property_capacity < reconstruction.property_count ||
1000  (reconstruction.property_count > 0 &&
1001  (reconstruction.property_per_particle == nullptr || attached_properties == nullptr)) ||
1002  (!reconstruction.empty && reconstruction.number <= amrex::Real(0.0)) ||
1003  (reconstruction.empty && (reconstruction.number != amrex::Real(0.0) ||
1004  reconstruction.particle_mass != amrex::Real(0.0)))) {
1005  return false;
1006  }
1007  for (int property = 0; property < reconstruction.property_count; ++property) {
1008  const amrex::Real value = reconstruction.property_per_particle[property];
1009  if (!remap_detail::finite_nonnegative(value) ||
1010  (reconstruction.empty && value != amrex::Real(0.0))) {
1011  return false;
1012  }
1013  }
1014  const bool contains = !reconstruction.empty && reconstruction.particle_mass >= lower &&
1015  (reconstruction.particle_mass < upper ||
1016  (include_upper && reconstruction.particle_mass == upper));
1017  IntegratedMomentsCoreResult next;
1018  if (contains) {
1019  next.number = reconstruction.number;
1020  if (!remap_detail::finite_product(reconstruction.number,
1021  reconstruction.particle_mass, next.water_mass)) {
1022  return false;
1023  }
1024  for (int property = 0; property < reconstruction.property_count; ++property) {
1025  amrex::Real amount = amrex::Real(0.0);
1026  if (!remap_detail::finite_product(reconstruction.number,
1027  reconstruction.property_per_particle[property], amount)) {
1028  return false;
1029  }
1030  }
1031  }
1032  for (int property = 0; property < reconstruction.property_count; ++property) {
1033  amrex::Real amount = amrex::Real(0.0);
1034  if (contains && !remap_detail::finite_product(reconstruction.number,
1035  reconstruction.property_per_particle[property], amount)) {
1036  return false;
1037  }
1038  attached_properties[property] = amount;
1039  }
1040  integral = next;
1041  return true;
1042 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool finite_product(const amrex::Real left, const amrex::Real right, amrex::Real &result) noexcept
Definition: ERF_SBMRemapping.H:198

Referenced by integrate_interval().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ inverse_two_moment()

std::pair< amrex::Real, amrex::Real > erf_sbm::inverse_two_moment ( amrex::Real  L,
amrex::Real  H,
amrex::Real  lower,
amrex::Real  upper 
)

Inverse transform with finite-state and nonnegative-endpoint checks.

288 {
289  return SpectralGrid::endpoints_to_two_moment(L, H, lower, upper);
290 }
Real H
Definition: ERF_InitCustomPert_MovingTerrain.H:7

◆ make_advection_face_policy()

constexpr AdvectionFacePolicy erf_sbm::make_advection_face_policy ( const AdvectionFacePolicyInput &  input)
constexprnoexcept

Decide the face-level spectral advection semantics without performing ERF's deferred physical-boundary ghost-fill lifecycle. At a physical face whose high-order stencil is not qualified, the admissible donor proposal remains available and high-order transport falls back to it.

60 {
61  using Action = AdvectionFaceAction;
62  if (input.kind == AdvectionFaceKind::Periodic) {
63  return {Action::PeriodicSharedFace, false, false, true};
64  }
65  if (input.kind == AdvectionFaceKind::ImpermeableWall ||
66  input.outward_carrier == amrex::Real(0.0)) {
67  return {Action::ZeroNormalFlux, false, false, false};
68  }
69 
70  if (input.outward_carrier > amrex::Real(0.0)) {
71  // Outward flow uses the interior spectrum. The donor path is always
72  // defined; high order is used only with a qualified physical stencil.
73  return {Action::InteriorDonor, true, false,
74  input.high_order_stencil_available};
75  }
76 
77  // Reverse flow contradicts a face declared as advective outflow. External
78  // spectral data must not silently change that declared boundary policy.
79  if (input.kind == AdvectionFaceKind::AdvectiveOutflow) {
80  return {Action::RejectInwardOutflow, false, false, false};
81  }
82 
83  // The remaining negative-carrier boundary category is prescribed inflow.
84  // A complete explicit atomic-group spectrum is required; qc/qr cannot
85  // supply missing bin or attached-property composition.
86  if (!input.explicit_spectral_state_present) {
87  return {Action::RejectMissingInflowState, false, false, false};
88  }
89  if (!input.explicit_spectral_group_complete) {
90  return {Action::RejectIncompleteInflowState, false, false, false};
91  }
92  return {Action::ExplicitSpectralInflow, false, true,
93  input.high_order_stencil_available};
94 }
AdvectionFaceAction
Definition: ERF_SBMAdvectionBoundary.H:17

◆ make_attached_property_support_descriptors()

std::vector< AttachedPropertySupportDescriptor > erf_sbm::make_attached_property_support_descriptors ( const SBMLayout &  layout)
215 {
216  std::vector<AttachedPropertySupportDescriptor> result;
217  const auto groups = make_constraint_groups(layout);
218  for (std::size_t group_index = 0; group_index < groups.size(); ++group_index) {
219  const auto& group = groups[group_index];
220  const auto population = std::find_if(layout.populations().begin(), layout.populations().end(),
221  [&](const PopulationLayout& candidate) {
222  return candidate.population_id == group.population_id;
223  });
224  if (population == layout.populations().end()) {
225  throw std::logic_error("SBM attached-property group references an unknown population");
226  }
227  for (const int property_index : group.attached_property_indices) {
228  const auto& property = layout.attached_properties()[static_cast<std::size_t>(property_index)];
229  AttachedPropertySupportDescriptor descriptor;
230  descriptor.group_index = static_cast<int>(group_index);
231  descriptor.property_index = property_index;
232  descriptor.property_component = layout.property_offset(property_index) + group.bin;
233  descriptor.mass_component = population->mass_offset + group.bin;
234  descriptor.number_component = population->number_offset >= 0 ?
235  population->number_offset + group.bin : -1;
236  descriptor.two_moment = descriptor.number_component >= 0 ? 1 : 0;
237  descriptor.carrier_component = descriptor.two_moment ?
238  descriptor.number_component : descriptor.mass_component;
239  descriptor.pivot = population->grid.pivot(group.bin);
240  // The transport envelope is expressed as S/carrier. For a
241  // two-moment carrier this is S/C, while a one-moment property
242  // uses C=M/m_pivot and therefore has normalized bounds
243  // (support_min/m_pivot, support_max/m_pivot).
244  const amrex::Real support_scale = descriptor.two_moment ?
245  amrex::Real(1.0) : descriptor.pivot;
246  descriptor.hard_min = property.support_min / support_scale;
247  descriptor.hard_max = property.support_max / support_scale;
248  descriptor.has_hard_min = finite(property.support_min) ? 1 : 0;
249  descriptor.has_hard_max = finite(property.support_max) ? 1 : 0;
250  result.push_back(descriptor);
251  }
252  }
253  return result;
254 }
Here is the call graph for this function:

◆ make_constraint_closure_chunks()

std::vector< ConstraintClosureChunk > erf_sbm::make_constraint_closure_chunks ( const SBMLayout &  layout,
const int  max_groups 
)
258 {
259  if (max_groups <= 0) {
260  throw std::invalid_argument("SBM scratch chunk size must be a positive number of complete groups");
261  }
262  const auto groups = make_constraint_groups(layout);
263  std::vector<ConstraintClosureChunk> chunks;
264  for (std::size_t group_index = 0; group_index < groups.size(); ++group_index) {
265  if (chunks.empty() ||
266  static_cast<int>(chunks.back().group_indices.size()) >= max_groups) {
267  chunks.push_back({});
268  }
269  auto& chunk = chunks.back();
270  chunk.group_indices.push_back(static_cast<int>(group_index));
271  for (const int component : groups[group_index].members) {
272  if (std::find(chunk.components.begin(), chunk.components.end(), component) == chunk.components.end()) {
273  chunk.components.push_back(component);
274  }
275  }
276  }
277  return chunks;
278 }
Here is the call graph for this function:

◆ make_constraint_descriptors()

FlattenedConstraintSet erf_sbm::make_constraint_descriptors ( const SBMLayout &  layout)

Flatten complete semantic groups into bounded device descriptors.

209 {
210  return flatten_constraint_groups(make_constraint_groups(layout), layout.ncomp());
211 }
FlattenedConstraintSet flatten_constraint_groups(const std::vector< ConstraintGroup > &groups, const int state_components)
Definition: ERF_SBMConstraintGroups.cpp:167
Here is the call graph for this function:

◆ make_constraint_groups()

std::vector< ConstraintGroup > erf_sbm::make_constraint_groups ( const SBMLayout &  layout)

Build deterministic groups from the immutable runtime layout.

78 {
79  std::vector<ConstraintGroup> groups;
80  for (const auto& population : layout.populations()) {
81  const int nbins = population.grid.nbins();
82  for (int bin = 0; bin < nbins; ++bin) {
83  ConstraintGroup group;
84  group.population_id = population.population_id;
85  group.bin = bin;
86  group.semantic_id = population.semantic_id + ".bin." + std::to_string(bin);
87  group.moment_mode = population.moment_mode;
88  const int mass = population.mass_offset + bin;
89  group.members.push_back(mass);
90  group.transport_members.push_back(mass);
91  const int number = population.number_offset >= 0 ? population.number_offset + bin : -1;
92  if (number >= 0) {
93  group.members.push_back(number);
94  group.transport_members.push_back(number);
95  const amrex::Real lower = population.grid.edges()[static_cast<std::size_t>(bin)];
96  const amrex::Real upper = population.grid.edges()[static_cast<std::size_t>(bin + 1)];
97  const amrex::Real denominator = upper - lower;
98  add_constraint(group, "number_nonnegative", {{number, amrex::Real(1.0)}});
99  add_constraint(group, "endpoint_low",
100  {{number, upper / denominator},
101  {mass, -amrex::Real(1.0) / denominator}});
102  add_constraint(group, "endpoint_high",
103  {{mass, amrex::Real(1.0) / denominator},
104  {number, -lower / denominator}});
105  } else {
106  add_constraint(group, "mass_nonnegative", {{mass, amrex::Real(1.0)}});
107  }
108 
109  for (std::size_t property_index = 0;
110  property_index < layout.attached_properties().size(); ++property_index) {
111  const auto& property = layout.attached_properties()[property_index];
112  if (property.carrier_population != population.population_id) continue;
113  const int property_component = layout.property_offset(static_cast<int>(property_index)) + bin;
114  group.members.push_back(property_component);
115  group.transport_members.push_back(property_component);
116  group.attached_property_indices.push_back(static_cast<int>(property_index));
117  add_constraint(group, property.semantic_id + ".nonnegative", {{property_component, amrex::Real(1.0)}});
118 
119  const bool has_lower = finite(property.support_min) && property.support_min > amrex::Real(0.0);
120  const bool has_upper = finite(property.support_max) &&
121  property.support_max >= property.support_min;
122  if (has_upper) {
123  if (number >= 0) {
124  // Two-moment storage is physical (M,C), so support
125  // bounds are bounds on the carrier number C.
126  add_constraint(group, property.semantic_id + ".support_upper",
127  {{number, property.support_max},
128  {property_component, -amrex::Real(1.0)}});
129  } else {
130  const amrex::Real pivot = population.grid.pivot(bin);
131  add_constraint(group, property.semantic_id + ".support_upper",
132  {{mass, property.support_max / pivot},
133  {property_component, -1.0}});
134  }
135  }
136  if (has_lower) {
137  if (number >= 0) {
138  add_constraint(group, property.semantic_id + ".support_lower",
139  {{number, -property.support_min},
140  {property_component, amrex::Real(1.0)}});
141  } else {
142  const amrex::Real pivot = population.grid.pivot(bin);
143  add_constraint(group, property.semantic_id + ".support_lower",
144  {{mass, -property.support_min / pivot},
145  {property_component, amrex::Real(1.0)}});
146  }
147  }
148 
149  if (property.kind == PropertyKind::MassBoundedSubset) {
150  if (number >= 0) {
151  // The carrier mass is already the authoritative M
152  // component in two-moment storage.
153  add_constraint(group, property.semantic_id + ".carrier_mass_minus_subset",
154  {{mass, amrex::Real(1.0)}, {property_component, -amrex::Real(1.0)}});
155  } else {
156  add_constraint(group, property.semantic_id + ".carrier_mass_minus_subset",
157  {{mass, amrex::Real(1.0)}, {property_component, -amrex::Real(1.0)}});
158  }
159  }
160  }
161  groups.push_back(std::move(group));
162  }
163  }
164  return groups;
165 }

Referenced by authoritative_state_admissible(), ERF::ERF_shared(), make_attached_property_support_descriptors(), make_constraint_closure_chunks(), and make_constraint_descriptors().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ plan_packet_routing()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE PacketRoutingPlan erf_sbm::plan_packet_routing ( const PopulationRemapView  population,
const amrex::Real  packet_number,
const amrex::Real  particle_mass 
)
noexcept

Classify one packet without allocation or state mutation. Two-moment routing uses half-open intervals, includes the global top edge, and admits only four representable steps of outer-boundary roundoff. The plan keeps the original packet mass while its destination records the exact boundary mass. The immutable metadata must have been fully qualified once before this call; the per-packet path performs only constant-time context checks and a logarithmic interval search.

510 {
511  PacketRoutingPlan plan;
512  plan.population = population;
513  plan.packet_number = packet_number;
514  plan.packet_particle_mass = particle_mass;
515  if (!valid_population_remap_context(population) ||
516  !amrex::Math::isfinite(packet_number) || !amrex::Math::isfinite(particle_mass) ||
517  packet_number < amrex::Real(0.0) || particle_mass < amrex::Real(0.0)) {
518  return plan;
519  }
520  if (packet_number == amrex::Real(0.0)) {
521  plan.status = RemapStatus::Ok;
522  return plan;
523  }
524 
525  if (population.moment_mode == MomentMode::TwoMoment) {
526  // A dry positive-number packet is residual material, not a populated
527  // zero-water liquid interval, even when the first edge is exactly zero.
528  if (particle_mass == amrex::Real(0.0)) {
529  plan.status = RemapStatus::ZeroWaterResidual;
530  plan.residual_number_weight = amrex::Real(1.0);
531  return plan;
532  }
533  if (particle_mass < population.edges[0]) {
535  particle_mass, population.edges[0], true)) {
536  plan.status = RemapStatus::BelowSupportedGrid;
537  return plan;
538  }
539  plan.status = RemapStatus::Ok;
540  plan.destination_count = 1;
541  plan.destinations[0] = {0, amrex::Real(1.0), population.edges[0]};
542  plan.normalized_roundoff = true;
543  return plan;
544  }
545  if (particle_mass > population.edges[population.nbins]) {
547  particle_mass, population.edges[population.nbins], false)) {
548  plan.status = RemapStatus::Overflow;
549  return plan;
550  }
551  plan.status = RemapStatus::Ok;
552  plan.destination_count = 1;
553  plan.destinations[0] = {population.nbins - 1, amrex::Real(1.0),
554  population.edges[population.nbins]};
555  plan.normalized_roundoff = true;
556  return plan;
557  }
558  const int bin = particle_mass == population.edges[population.nbins] ?
559  population.nbins - 1 :
560  amrex::bisect(population.edges, 0, population.nbins, particle_mass);
561  plan.status = RemapStatus::Ok;
562  plan.destination_count = 1;
563  plan.destinations[0] = {bin, amrex::Real(1.0), particle_mass};
564  return plan;
565  }
566 
567  if (particle_mass == amrex::Real(0.0)) {
568  plan.status = RemapStatus::ZeroWaterResidual;
569  plan.residual_number_weight = amrex::Real(1.0);
570  return plan;
571  }
572  if (particle_mass < population.pivots[0]) {
573  amrex::Real wet_weight = amrex::Real(0.0);
574  const auto quotient_status = remap_detail::checked_quotient(
575  particle_mass, population.pivots[0], wet_weight);
576  if (quotient_status != remap_detail::QuotientStatus::Ok) {
577  plan.status = remap_detail::remap_quotient_status(quotient_status);
578  return plan;
579  }
580  plan.status = RemapStatus::ZeroWaterResidual;
581  plan.destination_count = 1;
582  plan.destinations[0] = {0, wet_weight, population.pivots[0]};
583  plan.residual_number_weight = amrex::Real(1.0) - wet_weight;
584  return plan;
585  }
586  if (particle_mass > population.pivots[population.nbins - 1]) {
587  plan.status = RemapStatus::Overflow;
588  return plan;
589  }
590  const int lower_bin = particle_mass == population.pivots[population.nbins - 1] ?
591  population.nbins - 1 :
592  amrex::bisect(population.pivots, 0, population.nbins - 1, particle_mass);
593  if (particle_mass == population.pivots[lower_bin]) {
594  plan.status = RemapStatus::Ok;
595  plan.destination_count = 1;
596  plan.destinations[0] = {lower_bin, amrex::Real(1.0), population.pivots[lower_bin]};
597  return plan;
598  }
599  if (lower_bin + 1 < population.nbins) {
600  const amrex::Real lower = population.pivots[lower_bin];
601  const amrex::Real upper = population.pivots[lower_bin + 1];
602  const amrex::Real width = upper - lower;
603  amrex::Real lower_weight = amrex::Real(0.0);
604  amrex::Real upper_weight = amrex::Real(0.0);
605  const auto lower_status = remap_detail::checked_quotient(
606  upper - particle_mass, width, lower_weight);
607  const auto upper_status = remap_detail::checked_quotient(
608  particle_mass - lower, width, upper_weight);
609  if (lower_status != remap_detail::QuotientStatus::Ok ||
610  upper_status != remap_detail::QuotientStatus::Ok) {
611  const auto quotient_status =
612  lower_status != remap_detail::QuotientStatus::Ok ? lower_status : upper_status;
613  plan.status = remap_detail::remap_quotient_status(quotient_status);
614  return plan;
615  }
616  plan.status = RemapStatus::Ok;
617  plan.destination_count = 2;
618  plan.destinations[0] = {lower_bin, lower_weight, lower};
619  plan.destinations[1] = {lower_bin + 1, upper_weight, upper};
620  return plan;
621  }
622  return plan;
623 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool within_boundary_roundoff(const amrex::Real value, const amrex::Real endpoint, const bool lower_boundary) noexcept
Definition: ERF_SBMRemapping.H:220
Here is the call graph for this function:

◆ population_remap_view()

PopulationRemapView erf_sbm::population_remap_view ( const SBMLayout &  layout,
int  population_id 
)
noexcept

Host adapter building a stable view into the immutable SBMLayout.

10 {
11  const auto& populations = layout.populations();
12  const auto found = std::find_if(populations.begin(), populations.end(),
13  [population_id](const PopulationLayout& population) {
14  return population.population_id == population_id;
15  });
16  if (found == populations.end()) return {};
17 
18  PopulationRemapView view;
19  view.population_id = found->population_id;
20  view.moment_mode = found->moment_mode;
21  view.coordinate_kind = found->grid.coordinate_kind();
22  view.edges = found->grid.edges().data();
23  view.pivots = found->grid.pivots().data();
24  view.nbins = found->grid.nbins();
25  view.mass_offset = found->mass_offset;
26  view.number_offset = found->number_offset;
27  view.state_components = layout.ncomp();
28  view.property_component_offsets = found->property_component_offsets.data();
29  view.property_count = static_cast<int>(found->property_component_offsets.size());
30  // SBMLayout validates these immutable arrays and constructs the component
31  // offsets. This adapter is used on per-packet paths, so do not repeat the
32  // O(nbins + properties^2) full qualification here. Keep the full validator
33  // for arbitrary raw views; this layout-derived view needs only a constant-
34  // time context check before entering the device/core routines.
35  if (!valid_population_remap_context(view)) return {};
36  return view;
37 }

Referenced by apply_packet_routing(), authoritative_state_admissible(), project_reconstruction_bin(), reconstruct_bin(), erf_sbm::SBMTransport::SBMTransport(), and validate_fixture_initial_state_canonicality().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ project_reconstruction_bin()

bool erf_sbm::project_reconstruction_bin ( const SBMLayout &  layout,
const ReconstructionDelta &  reconstruction,
std::vector< amrex::Real > &  candidate_state 
)
128 {
129  const auto population = population_remap_view(layout, reconstruction.population_id);
130  ReconstructionDeltaView view;
131  view.population = population;
132  view.bin = reconstruction.bin;
133  view.empty = reconstruction.empty;
134  view.normalized_roundoff = reconstruction.normalized_roundoff;
135  view.number = reconstruction.number;
136  view.particle_mass = reconstruction.particle_mass;
137  view.property_per_particle = reconstruction.property_per_particle.data();
138  view.property_count = static_cast<int>(reconstruction.property_per_particle.size());
139  return project_reconstruction_bin_core(population, view, candidate_state.data(),
140  static_cast<int>(candidate_state.size()));
141 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool project_reconstruction_bin_core(const PopulationRemapView population, const ReconstructionDeltaView reconstruction, amrex::Real *candidate_state, const int state_count) noexcept
Definition: ERF_SBMRemapping.H:1046
Here is the call graph for this function:

◆ project_reconstruction_bin_core()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool erf_sbm::project_reconstruction_bin_core ( const PopulationRemapView  population,
const ReconstructionDeltaView  reconstruction,
amrex::Real *  candidate_state,
const int  state_count 
)
noexcept

Project one reconstructed bin into caller-owned state after full validation.

1050 {
1051  if (!valid_population_remap_context(population) ||
1052  !same_population_context(population, reconstruction.population) ||
1053  reconstruction.bin < 0 || reconstruction.bin >= population.nbins ||
1054  candidate_state == nullptr || state_count != population.state_components ||
1055  reconstruction.property_count != population.property_count ||
1056  (reconstruction.property_count > 0 && reconstruction.property_per_particle == nullptr) ||
1057  !remap_detail::finite_nonnegative(reconstruction.number) ||
1058  !remap_detail::finite_nonnegative(reconstruction.particle_mass) ||
1059  (!reconstruction.empty && reconstruction.number <= amrex::Real(0.0)) ||
1060  (reconstruction.empty && (reconstruction.number != amrex::Real(0.0) ||
1061  reconstruction.particle_mass != amrex::Real(0.0)))) {
1062  return false;
1063  }
1064  if (!reconstruction.empty) {
1065  if (population.moment_mode == MomentMode::OneMoment) {
1066  if (reconstruction.particle_mass != population.pivots[reconstruction.bin]) return false;
1067  }
1068  }
1069  amrex::Real mass = amrex::Real(0.0);
1070  if (!remap_detail::finite_product(reconstruction.number,
1071  reconstruction.particle_mass, mass)) {
1072  return false;
1073  }
1074  amrex::Real projected_number = reconstruction.number;
1075  if (population.moment_mode == MomentMode::OneMoment) {
1077  mass, population.pivots[reconstruction.bin], projected_number) !=
1078  remap_detail::QuotientStatus::Ok) return false;
1080  population, reconstruction.bin, projected_number, mass)) {
1081  return false;
1082  }
1083  for (int property = 0; property < population.property_count; ++property) {
1084  const amrex::Real value = reconstruction.property_per_particle[property];
1085  amrex::Real amount = amrex::Real(0.0);
1086  if (!remap_detail::finite_nonnegative(value) ||
1087  (reconstruction.empty && value != amrex::Real(0.0)) ||
1088  !remap_detail::finite_product(reconstruction.number, value, amount)) {
1089  return false;
1090  }
1091  if (projected_number == amrex::Real(0.0)) {
1092  if (amount != amrex::Real(0.0)) return false;
1093  } else {
1094  amrex::Real per_particle = amrex::Real(0.0);
1096  amount, projected_number, per_particle) !=
1097  remap_detail::QuotientStatus::Ok) return false;
1098  }
1099  }
1100  const int bin = reconstruction.bin;
1101  candidate_state[population.mass_offset + bin] = mass;
1102  if (population.number_offset >= 0) {
1103  candidate_state[population.number_offset + bin] = reconstruction.number;
1104  }
1105  for (int property = 0; property < population.property_count; ++property) {
1106  candidate_state[population.property_component_offsets[property] + bin] =
1107  reconstruction.number * reconstruction.property_per_particle[property];
1108  }
1109  return true;
1110 }

Referenced by project_reconstruction_bin().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ProlongCarrierRelativeSpectrum() [1/2]

bool erf_sbm::ProlongCarrierRelativeSpectrum ( const SBMLayout &  layout,
const SBMAMRStateView &  coarse,
const SBMAMRStateView &  fine_target,
const amrex::Geometry &  coarse_geometry,
const amrex::Geometry &  fine_geometry,
const amrex::IntVect &  ratio,
const int  fine_level,
MultiFab &  fine_candidate,
std::string &  diagnostic 
)
531 {
532  diagnostic.clear();
533  std::string ratio_diagnostic;
534  const bool ratio_ok = valid_ratio(ratio, ratio_diagnostic);
535  const bool level_ok = fine_level > 0;
536 
537  std::string coarse_structure_diagnostic;
538  std::string fine_structure_diagnostic;
539  const bool coarse_structure_ok = validate_view_structure(
540  coarse, layout, "coarse", coarse_structure_diagnostic);
541  const bool fine_structure_ok = validate_view_structure(
542  fine_target, layout, "fine target", fine_structure_diagnostic);
543  const bool same_time = coarse.spectrum_time == fine_target.spectrum_time;
544  const bool local_structure_ok = ratio_ok && level_ok && coarse_structure_ok &&
545  fine_structure_ok && same_time;
546  const std::string structure_diagnostic =
547  !ratio_ok ? ratio_diagnostic
548  : !level_ok ? "prolongation fine level must be greater than zero"
549  : !coarse_structure_ok ? coarse_structure_diagnostic
550  : !fine_structure_ok ? fine_structure_diagnostic
551  : !same_time ? "prolongation requires same-time source and target tuples"
552  : std::string{};
553  if (!collective_all_true(
554  local_structure_ok, structure_diagnostic,
555  "SBM prolongation input tuple is invalid on another MPI rank",
556  diagnostic)) {
557  return false;
558  }
559 
560  const bool candidate_layout_ok =
561  fine_candidate.nComp() == layout.ncomp() &&
562  erf_auxiliary::SameCellLayout(fine_candidate, *fine_target.spectrum);
563  const bool separate_from_coarse = candidate_is_separate(fine_candidate, coarse);
564  const bool separate_from_fine_target =
565  candidate_is_separate(fine_candidate, fine_target);
566  bool local_candidate_ok = candidate_layout_ok && separate_from_coarse &&
567  separate_from_fine_target;
568  std::string local_candidate_diagnostic =
569  "prolongation candidate must be separate from both input tuples and match the fine SBM layout";
570  if (local_candidate_ok) {
571  for (int direction = 0; direction < AMREX_SPACEDIM; ++direction) {
572  if (!coarse_geometry.isPeriodic(direction) ||
573  !fine_geometry.isPeriodic(direction)) {
574  local_candidate_ok = false;
575  local_candidate_diagnostic =
576  "M4a carrier-relative prolongation requires periodic "
577  "geometry";
578  break;
579  }
580  }
581  }
582  if (local_candidate_ok) {
583  amrex::Box refined_coarse_domain = coarse_geometry.Domain();
584  refined_coarse_domain.refine(ratio);
585  if (refined_coarse_domain != fine_geometry.Domain()) {
586  local_candidate_ok = false;
587  local_candidate_diagnostic =
588  "prolongation refinement ratio does not map the coarse domain "
589  "to the fine domain";
590  }
591  }
592  if (!collective_all_true(
593  local_candidate_ok, local_candidate_diagnostic,
594  "SBM prolongation candidate or geometry is invalid on another MPI rank",
595  diagnostic)) {
596  return false;
597  }
598 
599  std::string coarse_carrier_diagnostic;
600  std::string fine_carrier_diagnostic;
601  const bool coarse_carriers_ok = validate_view_carriers(
602  coarse, "coarse", coarse_carrier_diagnostic);
603  const bool fine_carriers_ok = validate_view_carriers(
604  fine_target, "fine target", fine_carrier_diagnostic);
605  std::string coarse_spectrum_diagnostic;
606  const bool coarse_spectrum_ok = validate_view_spectrum(
607  coarse, layout, fine_level - 1, "coarse", coarse_spectrum_diagnostic);
608  const bool local_sources_ok = coarse_carriers_ok && fine_carriers_ok &&
609  coarse_spectrum_ok;
610  const std::string source_diagnostic =
611  !coarse_carriers_ok ? coarse_carrier_diagnostic
612  : !fine_carriers_ok ? fine_carrier_diagnostic
613  : !coarse_spectrum_ok ? coarse_spectrum_diagnostic
614  : std::string{};
615  if (!collective_all_true(
616  local_sources_ok, source_diagnostic,
617  "SBM prolongation source is inadmissible on another MPI rank",
618  diagnostic)) {
619  return false;
620  }
621 
622  MultiFab coarse_z(coarse.spectrum->boxArray(),
623  coarse.spectrum->DistributionMap(), layout.ncomp(), 0);
624  const bool coarse_z_ok = form_carrier_relative_state(
625  *coarse.spectrum, *coarse.dry_air_density,
626  coarse.density_component, coarse_z, diagnostic);
627  if (!collective_all_true(
628  coarse_z_ok, diagnostic,
629  "forming coarse carrier-relative SBM state failed on another MPI rank",
630  diagnostic)) {
631  return false;
632  }
633 
634  MultiFab fine_z(fine_target.spectrum->boxArray(),
635  fine_target.spectrum->DistributionMap(), layout.ncomp(), 0);
636  amrex::PhysBCFunctNoOp no_physical_boundary;
637  amrex::Vector<amrex::BCRec> bcs(static_cast<std::size_t>(layout.ncomp()));
638  // PCInterp currently ignores these records under the periodic-only M4a
639  // gate. Keep every face explicit; this is not a nonperiodic BC policy.
640  for (auto& bc : bcs) {
641  for (int direction = 0; direction < AMREX_SPACEDIM; ++direction) {
642  bc.setLo(direction, amrex::BCType::int_dir);
643  bc.setHi(direction, amrex::BCType::int_dir);
644  }
645  }
646  amrex::InterpFromCoarseLevel(
647  fine_z, amrex::IntVect(0), static_cast<Real>(coarse.spectrum_time),
648  coarse_z, 0, 0, layout.ncomp(), coarse_geometry, fine_geometry,
649  no_physical_boundary, 0, no_physical_boundary, 0, ratio,
650  &amrex::pc_interp, bcs, 0);
651 
652  MultiFab fine_candidate_scratch(fine_target.spectrum->boxArray(),
653  fine_target.spectrum->DistributionMap(),
654  layout.ncomp(), 0);
655  const bool reconstructed_ok = reconstruct_carrier_relative_state(
656  fine_z, *fine_target.dry_air_density, fine_target.density_component,
657  *fine_target.mapped_measure, fine_target.measure_component,
658  fine_candidate_scratch, diagnostic);
659  std::string candidate_admission_diagnostic;
660  const bool candidate_admissible = authoritative_state_admissible(
661  fine_candidate_scratch, layout, fine_level,
662  &candidate_admission_diagnostic);
663  const bool local_candidate_admissible = reconstructed_ok &&
664  candidate_admissible;
665  const std::string candidate_diagnostic =
666  !reconstructed_ok ? diagnostic
667  : !candidate_admissible
668  ? "prolonged SBM candidate is inadmissible: " +
669  candidate_admission_diagnostic
670  : std::string{};
671  if (!collective_all_true(
672  local_candidate_admissible, candidate_diagnostic,
673  "prolonged SBM candidate is inadmissible on another MPI rank",
674  diagnostic)) {
675  return false;
676  }
677  MultiFab::Copy(fine_candidate, fine_candidate_scratch, 0, 0, layout.ncomp(),
678  0);
679  return true;
680 }
@ int_dir
Definition: ERF_IndexDefines.H:297
bool SameCellLayout(const amrex::MultiFab &lhs, const amrex::MultiFab &rhs)
Definition: ERF_AuxiliaryMappedTransport.cpp:36
bool authoritative_state_admissible(const amrex::MultiFab &spectrum, const SBMLayout &layout, const int level, std::string *diagnostic)
Definition: ERF_SBMRestart.cpp:39
real(c_double), private bc
Definition: ERF_module_mp_morr_two_moment.F90:182
Here is the call graph for this function:

◆ ProlongCarrierRelativeSpectrum() [2/2]

bool erf_sbm::ProlongCarrierRelativeSpectrum ( const SBMLayout &  layout,
const SBMAMRStateView &  coarse,
const SBMAMRStateView &  fine_target,
const amrex::Geometry &  coarse_geometry,
const amrex::Geometry &  fine_geometry,
const amrex::IntVect &  ratio,
int  fine_level,
amrex::MultiFab &  fine_candidate,
std::string &  diagnostic 
)

Form a same-time piecewise-constant carrier-relative fine candidate. On success, overwrite the destination's valid cells with the complete, admitted result. On failure (including a rejection on another MPI rank), leave every destination valid cell and all source views unchanged. This operator does not itself commit authoritative SBM state.

◆ property_support_is_admissible()

bool erf_sbm::property_support_is_admissible ( amrex::Real  property,
amrex::Real  carrier_number,
amrex::Real  lower,
amrex::Real  upper,
amrex::Real  tolerance = 0.0 
)
noexcept

Host reference for the finite property-support constraints.

297 {
298  if (!finite(property) || !finite(carrier_number) || !finite(lower) || !finite(upper) ||
299  carrier_number < amrex::Real(0.0) || lower < amrex::Real(0.0) || upper < lower) return false;
300  if (carrier_number == amrex::Real(0.0)) {
301  return std::abs(property) <= tolerance;
302  }
303  return property >= lower * carrier_number - tolerance &&
304  property <= upper * carrier_number + tolerance;
305 }
Here is the call graph for this function:

◆ reconstruct_bin()

ReconstructionStatus erf_sbm::reconstruct_bin ( const SBMLayout &  layout,
const int  population_id,
const int  bin,
const std::vector< amrex::Real > &  persisted_state,
ReconstructionDelta &  reconstruction 
)
75 {
76  const auto population = population_remap_view(layout, population_id);
77  std::vector<amrex::Real> properties(
78  population.property_count >= 0 ? static_cast<std::size_t>(population.property_count) : 0u,
79  amrex::Real(0.0));
80  ReconstructionDeltaView view;
81  const auto status = reconstruct_bin_core(
82  population, bin, persisted_state.data(), static_cast<int>(persisted_state.size()),
83  properties.data(), static_cast<int>(properties.size()), view);
84  if (status == ReconstructionStatus::Invalid) return status;
85 
86  ReconstructionDelta next;
87  next.population_id = population_id;
88  next.bin = bin;
89  next.empty = view.empty;
90  next.normalized_roundoff = view.normalized_roundoff;
91  next.number = view.number;
92  next.particle_mass = view.particle_mass;
93  next.property_per_particle = std::move(properties);
94  reconstruction = std::move(next);
95  return status;
96 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE ReconstructionStatus reconstruct_bin_core(const PopulationRemapView population, const int bin, const amrex::Real *persisted_state, const int state_count, amrex::Real *property_per_particle, const int property_capacity, ReconstructionDeltaView &reconstruction) noexcept
Definition: ERF_SBMRemapping.H:898
Here is the call graph for this function:

◆ reconstruct_bin_core()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE ReconstructionStatus erf_sbm::reconstruct_bin_core ( const PopulationRemapView  population,
const int  bin,
const amrex::Real *  persisted_state,
const int  state_count,
amrex::Real *  property_per_particle,
const int  property_capacity,
ReconstructionDeltaView &  reconstruction 
)
noexcept
903 {
904  if (!valid_population_remap_context(population) || bin < 0 || bin >= population.nbins ||
905  persisted_state == nullptr || state_count != population.state_components ||
906  property_capacity < population.property_count ||
907  (population.property_count > 0 && property_per_particle == nullptr)) {
908  return ReconstructionStatus::Invalid;
909  }
911  population, bin, persisted_state, state_count)) {
912  return ReconstructionStatus::Invalid;
913  }
914  const amrex::Real mass = persisted_state[population.mass_offset + bin];
915  if (!remap_detail::finite_nonnegative(mass)) return ReconstructionStatus::Invalid;
916  ReconstructionDeltaView next;
917  next.population = population;
918  next.bin = bin;
919  next.property_per_particle = property_per_particle;
920  next.property_count = population.property_count;
921  if (population.moment_mode == MomentMode::OneMoment) {
922  next.particle_mass = population.pivots[bin];
924  mass, next.particle_mass, next.number) != remap_detail::QuotientStatus::Ok) {
925  return ReconstructionStatus::Invalid;
926  }
927  } else {
928  const amrex::Real number = persisted_state[population.number_offset + bin];
929  if (!remap_detail::finite_nonnegative(number)) return ReconstructionStatus::Invalid;
931  population, bin, persisted_state, state_count)) {
932  return ReconstructionStatus::Invalid;
933  }
934  const amrex::Real lower = population.edges[bin];
935  const amrex::Real upper = population.edges[bin + 1];
936  EndpointTransform endpoints;
937  if (!try_two_moment_to_endpoints(number, mass, lower, upper, endpoints)) {
938  return ReconstructionStatus::Invalid;
939  }
940  next.number = number;
941  // Endpoint tolerance is only a realizability aid for the transform.
942  // Persisted state has already passed strict canonical ownership and is
943  // never normalized by no-process reconstruction.
944  next.normalized_roundoff = false;
945  if (number > amrex::Real(0.0)) {
947  mass, number, next.particle_mass) != remap_detail::QuotientStatus::Ok) {
948  return ReconstructionStatus::Invalid;
949  }
950  }
951  }
952  next.empty = next.number == amrex::Real(0.0);
953  if (next.empty) next.particle_mass = amrex::Real(0.0);
954  for (int property = 0; property < population.property_count; ++property) {
955  const amrex::Real amount = persisted_state[
956  population.property_component_offsets[property] + bin];
957  if (!remap_detail::finite_nonnegative(amount)) return ReconstructionStatus::Invalid;
958  if (next.empty) {
959  if (amount != amrex::Real(0.0)) return ReconstructionStatus::Invalid;
960  } else {
961  amrex::Real per_particle = amrex::Real(0.0);
963  amount, next.number, per_particle) != remap_detail::QuotientStatus::Ok) {
964  return ReconstructionStatus::Invalid;
965  }
966  }
967  }
968  for (int property = 0; property < population.property_count; ++property) {
969  const amrex::Real amount = persisted_state[
970  population.property_component_offsets[property] + bin];
971  if (next.empty) {
972  property_per_particle[property] = amrex::Real(0.0);
974  amount, next.number, property_per_particle[property]) !=
975  remap_detail::QuotientStatus::Ok) {
976  return ReconstructionStatus::Invalid;
977  }
978  }
979  reconstruction = next;
980  return next.empty ? ReconstructionStatus::Empty : ReconstructionStatus::Populated;
981 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool canonical_two_moment_bin_state(const PopulationRemapView &population, const int bin, const RemapCellStateView &state, const int state_count) noexcept
Definition: ERF_SBMRemapping.H:416
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool canonical_persisted_bin_state(const PopulationRemapView &population, const int bin, const RemapCellStateView &state, const int state_count) noexcept
Definition: ERF_SBMRemapping.H:485
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool try_two_moment_to_endpoints(const amrex::Real C, const amrex::Real M, const amrex::Real lower, const amrex::Real upper, EndpointTransform &result) noexcept
Definition: ERF_SpectralGrid.H:48

Referenced by reconstruct_bin().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ representation_identity()

constexpr RepresentationIdentity erf_sbm::representation_identity ( const MomentMode  mode)
constexprnoexcept
17 {
18  switch (mode) {
19  case MomentMode::OneMoment:
20  return {"fixed-pivot-1m-v1", "fixed-pivot-delta-v1", "fixed-pivot-two-center-v1"};
21  case MomentMode::TwoMoment:
22  return {"interval-2m-v1", "mean-delta-2m-v1", "actual-mass-interval-v1"};
23  }
24  return {"invalid-representation", "invalid-reconstruction", "invalid-packet-remap"};
25 }

Referenced by erf_sbm::SBMLayout::inspection(), and erf_sbm::SBMLayout::SBMLayout().

Here is the caller graph for this function:

◆ require_host_write_allowed()

void erf_sbm::require_host_write_allowed ( bool  sbm_active,
int  component,
HostWritePath  path 
)

Fail closed when a native host writer reaches a component owned by SBM.

43 {
44  require_host_write_range_allowed(sbm_active, component, 1, path);
45 }
void require_host_write_range_allowed(const bool sbm_active, const int start_component, const int num_components, const HostWritePath path)
Definition: ERF_SBMOwnership.cpp:47
Here is the call graph for this function:

◆ require_host_write_range_allowed()

void erf_sbm::require_host_write_range_allowed ( bool  sbm_active,
int  start_component,
int  num_components,
HostWritePath  path 
)

Fail closed when any component in a native writer's range is owned by SBM.

49 {
50  if (!host_write_range_allowed(sbm_active, start_component, num_components)) {
51  const std::string range = num_components > 0 && start_component >= 0 &&
52  num_components <= std::numeric_limits<int>::max() - start_component
53  ? " [" + std::to_string(start_component) + "," +
54  std::to_string(start_component + num_components) + ")"
55  : " (invalid range)";
56  amrex::Abort(std::string("SBM owns compact qc/qr; native ") +
57  host_write_path_name(path) + " write denied for component range" + range);
58  }
59 }
const char * host_write_path_name(const HostWritePath path) noexcept
Definition: ERF_SBMOwnership.cpp:9

Referenced by erf_slow_rhs_post(), if(), and require_host_write_allowed().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ restart_projection_matches()

bool erf_sbm::restart_projection_matches ( const amrex::MultiFab &  spectrum,
const amrex::MultiFab &  persisted_core,
const SBMBulkProjection &  projection,
int  qc_component,
int  qr_component,
amrex::Real  tolerance_scale = amrex::Real(128.0) 
)

Compare the projected spectrum to checkpointed qc/qr without mutating core.

188 {
189  if (spectrum.boxArray() != persisted_core.boxArray() ||
190  spectrum.DistributionMap() != persisted_core.DistributionMap() ||
191  qc_component < 0 || qr_component < 0 ||
192  qc_component >= persisted_core.nComp() || qr_component >= persisted_core.nComp() ||
193  !std::isfinite(tolerance_scale) || !(tolerance_scale >= amrex::Real(0.0)) ||
194  !spectrum.is_finite(0, spectrum.nComp(), 0) ||
195  !persisted_core.is_finite(qc_component, 1, 0) ||
196  !persisted_core.is_finite(qr_component, 1, 0)) {
197  return false;
198  }
199 
200  amrex::MultiFab expected(persisted_core.boxArray(), persisted_core.DistributionMap(), 2, 0);
201  amrex::MultiFab stored(persisted_core.boxArray(), persisted_core.DistributionMap(), 2, 0);
202  expected.setVal(amrex::Real(0.0));
203  for (amrex::MFIter mfi(expected); mfi.isValid(); ++mfi) {
204  projection.apply_to_core(mfi.validbox(), spectrum.const_array(mfi),
205  expected.array(mfi), 0, 1);
206  }
207  if (!expected.is_finite(0, 2, 0)) return false;
208  amrex::MultiFab::Copy(stored, persisted_core, qc_component, 0, 1, 0);
209  amrex::MultiFab::Copy(stored, persisted_core, qr_component, 1, 1, 0);
210  if (!stored.is_finite(0, 2, 0)) return false;
211 
212  const amrex::Real scale = std::max({expected.norm0(0), expected.norm0(1),
213  stored.norm0(0), stored.norm0(1)});
214  amrex::MultiFab::Subtract(expected, stored, 0, 0, 2, 0);
215  if (!expected.is_finite(0, 2, 0)) return false;
216  const amrex::Real difference = std::max(expected.norm0(0), expected.norm0(1));
217  const amrex::Real tolerance = tolerance_scale *
219  std::max(scale, std::numeric_limits<amrex::Real>::min());
220  return std::isfinite(scale) && std::isfinite(difference) &&
221  std::isfinite(tolerance) && difference <= tolerance;
222 }

Referenced by ERF::ReadCheckpointFile().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ restart_schema()

std::string erf_sbm::restart_schema ( const SBMLayout &  layout)

Exact semantic restart schema; no implicit checkpoint migration is performed.

23 {
24  std::ostringstream schema;
25  schema << "ERF-SBM-RESTART-v1\n"
26  << "layout=" << layout.schema_identity() << '\n'
27  << "constraint-policy=linear-groups-plus-canonical-persisted-v1\n"
28  << "projection=liquid-mass-sum-to-qc-qr-v1\n"
29  << "transport=sbm-m3-mapped-group-fct-v1\n"
30  << "high-order=endpoint-number-wenoz3-v1\n";
31  return schema.str();
32 }

Referenced by restart_schema_matches(), and ERF::WriteCheckpointFile().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ restart_schema_matches()

bool erf_sbm::restart_schema_matches ( const SBMLayout &  layout,
const std::string &  persisted 
)

Compare persisted and expected schemas byte-for-byte.

35 {
36  return restart_schema(layout) == persisted;
37 }
std::string restart_schema(const SBMLayout &layout)
Definition: ERF_SBMRestart.cpp:22

Referenced by ERF::ReadCheckpointFile().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ RestrictMappedSpectrum() [1/2]

bool erf_sbm::RestrictMappedSpectrum ( const SBMLayout &  layout,
const SBMAMRStateView &  fine,
const SBMAMRStateView &  coarse,
const amrex::IntVect &  ratio,
const int  coarse_level,
MultiFab &  coarse_candidate,
std::string &  diagnostic 
)
324 {
325  diagnostic.clear();
326  std::string ratio_diagnostic;
327  const bool ratio_ok = valid_ratio(ratio, ratio_diagnostic);
328  const bool level_ok = coarse_level >= 0;
329  std::string fine_structure_diagnostic;
330  std::string coarse_structure_diagnostic;
331  const bool fine_structure_ok = validate_view_structure(
332  fine, layout, "fine", fine_structure_diagnostic);
333  const bool coarse_structure_ok = validate_view_structure(
334  coarse, layout, "coarse", coarse_structure_diagnostic);
335  const bool same_time = fine.spectrum_time == coarse.spectrum_time;
336  // average_down requires complete fine children. Do not query coarsenability
337  // until the ratio is positive and the fine spectrum pointer is valid.
338  const bool fine_boxes_coarsenable =
339  ratio_ok && fine_structure_ok &&
340  fine.spectrum->boxArray().coarsenable(ratio);
341  const bool local_structure_ok = ratio_ok && level_ok && fine_structure_ok &&
342  coarse_structure_ok && same_time &&
343  fine_boxes_coarsenable;
344  const std::string structure_diagnostic =
345  !ratio_ok ? ratio_diagnostic
346  : !level_ok ? "restriction coarse level must be nonnegative"
347  : !fine_structure_ok ? fine_structure_diagnostic
348  : !coarse_structure_ok ? coarse_structure_diagnostic
349  : !same_time ? "restriction requires fine and coarse state tuples at the same semantic time"
350  : !fine_boxes_coarsenable
351  ? "restriction fine BoxArray is not coarsenable by the refinement ratio; fine boxes must align to complete coarse cells"
352  : std::string{};
353  if (!collective_all_true(
354  local_structure_ok, structure_diagnostic,
355  "SBM restriction input tuple is invalid on another MPI rank",
356  diagnostic)) {
357  return false;
358  }
359 
360  const bool candidate_layout_ok =
361  coarse_candidate.nComp() == layout.ncomp() &&
362  erf_auxiliary::SameCellLayout(coarse_candidate, *coarse.spectrum);
363  const bool separate_from_fine = candidate_is_separate(coarse_candidate, fine);
364  const bool separate_from_coarse = candidate_is_separate(coarse_candidate, coarse);
365  const bool local_candidate_ok = candidate_layout_ok && separate_from_fine &&
366  separate_from_coarse;
367  if (!collective_all_true(
368  local_candidate_ok,
369  "restriction candidate must be separate from both input tuples and match the coarse SBM layout",
370  "SBM restriction candidate aliases an input on another MPI rank",
371  diagnostic)) {
372  return false;
373  }
374 
375  std::string fine_carrier_diagnostic;
376  std::string coarse_carrier_diagnostic;
377  const bool fine_carriers_ok = validate_view_carriers(
378  fine, "fine", fine_carrier_diagnostic);
379  const bool coarse_carriers_ok = validate_view_carriers(
380  coarse, "coarse", coarse_carrier_diagnostic);
381  std::string fine_spectrum_diagnostic;
382  std::string coarse_spectrum_diagnostic;
383  const bool fine_spectrum_ok = validate_view_spectrum(
384  fine, layout, coarse_level + 1, "fine", fine_spectrum_diagnostic);
385  const bool coarse_spectrum_ok = validate_view_spectrum(
386  coarse, layout, coarse_level, "coarse", coarse_spectrum_diagnostic);
387  const bool local_sources_ok = fine_carriers_ok && coarse_carriers_ok &&
388  fine_spectrum_ok && coarse_spectrum_ok;
389  const std::string source_diagnostic =
390  !fine_carriers_ok ? fine_carrier_diagnostic
391  : !coarse_carriers_ok ? coarse_carrier_diagnostic
392  : !fine_spectrum_ok ? fine_spectrum_diagnostic
393  : !coarse_spectrum_ok ? coarse_spectrum_diagnostic
394  : std::string{};
395  if (!collective_all_true(
396  local_sources_ok, source_diagnostic,
397  "SBM restriction source is inadmissible on another MPI rank",
398  diagnostic)) {
399  return false;
400  }
401 
402  MultiFab fine_h(fine.spectrum->boxArray(), fine.spectrum->DistributionMap(),
403  layout.ncomp(), 0);
404  MultiFab coarse_h(coarse.spectrum->boxArray(), coarse.spectrum->DistributionMap(),
405  layout.ncomp(), 0);
406  const int coverage_component = layout.ncomp();
407  const int ncomp = layout.ncomp();
408  MultiFab fine_support(fine.spectrum->boxArray(),
409  fine.spectrum->DistributionMap(), layout.ncomp() + 1,
410  0);
411  MultiFab coarse_support(coarse.spectrum->boxArray(),
412  coarse.spectrum->DistributionMap(),
413  layout.ncomp() + 1, 0);
414  std::string fine_mapped_diagnostic;
415  const bool fine_mapped_ok = form_mapped_state(
416  *fine.spectrum, *fine.mapped_measure, fine.measure_component, fine_h,
417  fine_mapped_diagnostic);
418  if (!collective_all_true(
419  fine_mapped_ok, fine_mapped_diagnostic,
420  "SBM restriction mapped-state formation failed on another MPI rank",
421  diagnostic)) {
422  return false;
423  }
424 
425  fine_support.setVal(Real(0.0));
426  for (amrex::MFIter mfi(fine_h, amrex::TilingIfNotGPU()); mfi.isValid();
427  ++mfi) {
428  const amrex::Box box = mfi.tilebox();
429  const auto mapped = fine_h.const_array(mfi);
430  const auto support = fine_support.array(mfi);
432  box, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
433  for (int component = 0; component < ncomp; ++component) {
434  support(i, j, k, component) =
435  mapped(i, j, k, component) > Real(0.0) ? Real(1.0)
436  : Real(0.0);
437  }
438  support(i, j, k, coverage_component) = Real(1.0);
439  });
440  }
441 
442  // Both averages use the same fine coverage. The extra support components
443  // remember whether any child had positive mapped inventory, even if the
444  // inventory average itself rounds to zero.
445  coarse_h.setVal(Real(0.0));
446  coarse_support.setVal(Real(0.0));
447  amrex::average_down(fine_h, coarse_h, 0, layout.ncomp(), ratio);
448  amrex::average_down(fine_support, coarse_support, 0, layout.ncomp() + 1,
449  ratio);
450 
451  MultiFab lost_positive_support(coarse_h.boxArray(),
452  coarse_h.DistributionMap(), 1, 0);
453  lost_positive_support.setVal(Real(0.0));
454  for (amrex::MFIter mfi(coarse_h, amrex::TilingIfNotGPU()); mfi.isValid();
455  ++mfi) {
456  const amrex::Box box = mfi.tilebox();
457  const auto mapped = coarse_h.const_array(mfi);
458  const auto support = coarse_support.const_array(mfi);
459  const auto bad = lost_positive_support.array(mfi);
461  box, [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept {
462  int cell_bad = 0;
463  if (support(i, j, k, coverage_component) > Real(0.0)) {
464  for (int component = 0; component < ncomp; ++component) {
465  if (support(i, j, k, component) > Real(0.0) &&
466  mapped(i, j, k, component) == Real(0.0)) {
467  cell_bad = 1;
468  }
469  }
470  }
471  bad(i, j, k, 0) = static_cast<Real>(cell_bad);
472  });
473  }
474  // The nonnegative bad mask has max zero when clear; an empty rank's local
475  // MultiFab maximum is negative, so both cases satisfy this predicate.
476  const bool local_support_preserved =
477  lost_positive_support.max(0, 0, true) <= Real(0.0);
478  if (!collective_all_true(local_support_preserved,
479  "restriction averaging underflow erased positive "
480  "mapped support to zero",
481  "restriction averaging underflow erased positive "
482  "mapped support on another MPI rank",
483  diagnostic)) {
484  return false;
485  }
486 
487  // Build the complete candidate independently. Only cells represented by
488  // fine coverage are reconstructed, so uncovered coarse values never take
489  // an unnecessary multiply/divide round trip. The public destination is
490  // untouched until every rank has admitted this scratch result.
491  MultiFab coarse_candidate_scratch(coarse.spectrum->boxArray(),
492  coarse.spectrum->DistributionMap(),
493  layout.ncomp(), 0);
494  MultiFab::Copy(coarse_candidate_scratch, *coarse.spectrum, 0, 0,
495  layout.ncomp(), 0);
496  const bool divided_ok = divide_mapped_state(
497  coarse_h, *coarse.mapped_measure, coarse.measure_component,
498  coarse_support, coverage_component, coarse_candidate_scratch,
499  diagnostic);
500  std::string candidate_admission_diagnostic;
501  const bool candidate_admissible = authoritative_state_admissible(
502  coarse_candidate_scratch, layout, coarse_level,
503  &candidate_admission_diagnostic);
504  const bool local_candidate_admissible = divided_ok && candidate_admissible;
505  const std::string candidate_diagnostic =
506  !divided_ok ? diagnostic
507  : !candidate_admissible
508  ? "restricted SBM candidate is inadmissible: " +
509  candidate_admission_diagnostic
510  : std::string{};
511  if (!collective_all_true(
512  local_candidate_admissible, candidate_diagnostic,
513  "restricted SBM candidate is inadmissible on another MPI rank",
514  diagnostic)) {
515  return false;
516  }
517  MultiFab::Copy(coarse_candidate, coarse_candidate_scratch, 0, 0,
518  layout.ncomp(), 0);
519  return true;
520 }
ParallelFor(fab_box, [=] AMREX_GPU_DEVICE(int i, int j, int k) { qrcuten_arr(i, j, k)=Real(0);qscuten_arr(i, j, k)=Real(0);qicuten_arr(i, j, k)=Real(0);})
Here is the call graph for this function:

◆ RestrictMappedSpectrum() [2/2]

bool erf_sbm::RestrictMappedSpectrum ( const SBMLayout &  layout,
const SBMAMRStateView &  fine,
const SBMAMRStateView &  coarse,
const amrex::IntVect &  ratio,
int  coarse_level,
amrex::MultiFab &  coarse_candidate,
std::string &  diagnostic 
)

Form an H-conservative coarse candidate from matching-time spectral views. On success, overwrite the destination's valid cells with the complete, admitted result. On failure (including a rejection on another MPI rank), leave every destination valid cell and all source views unchanged. This operator does not itself commit authoritative SBM state.

◆ same_population_context()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool erf_sbm::same_population_context ( const PopulationRemapView &  left,
const PopulationRemapView &  right 
)
noexcept
129 {
130  return left.population_id == right.population_id &&
131  left.moment_mode == right.moment_mode &&
132  left.coordinate_kind == right.coordinate_kind &&
133  left.edges == right.edges && left.pivots == right.pivots &&
134  left.nbins == right.nbins && left.mass_offset == right.mass_offset &&
135  left.number_offset == right.number_offset &&
136  left.state_components == right.state_components &&
137  left.property_component_offsets == right.property_component_offsets &&
138  left.property_count == right.property_count;
139 }

Referenced by apply_packet_routing_core(), project_reconstruction_bin_core(), and erf_sbm::remap_detail::route_plan_is_consistent().

Here is the caller graph for this function:

◆ transform_two_moment()

EndpointTransform erf_sbm::transform_two_moment ( amrex::Real  C,
amrex::Real  M,
amrex::Real  lower,
amrex::Real  upper 
)

Compatibility entry point delegating to the canonical grid-layer transform.

282 {
283  return SpectralGrid::two_moment_to_endpoints(C, M, lower, upper);
284 }

◆ try_endpoints_to_two_moment()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool erf_sbm::try_endpoints_to_two_moment ( const amrex::Real  L,
const amrex::Real  H,
const amrex::Real  lower,
const amrex::Real  upper,
amrex::Real &  C,
amrex::Real &  M 
)
noexcept

Allocation-free canonical inverse transform for host and device callers.

92 {
93  if (!amrex::Math::isfinite(L) || !amrex::Math::isfinite(H) || !amrex::Math::isfinite(lower) ||
94  !amrex::Math::isfinite(upper) || L < amrex::Real(0.0) || H < amrex::Real(0.0) ||
95  lower < amrex::Real(0.0) || !(lower < upper)) {
96  return false;
97  }
98  const amrex::Real count = L + H;
99  const amrex::Real mass = std::fma(lower, L, upper * H);
100  if (!amrex::Math::isfinite(count) || !amrex::Math::isfinite(mass) ||
101  count < amrex::Real(0.0) || mass < amrex::Real(0.0)) {
102  return false;
103  }
104  C = count;
105  M = mass;
106  return true;
107 }

Referenced by erf_sbm::SpectralGrid::endpoints_to_two_moment().

Here is the caller graph for this function:

◆ try_two_moment_to_endpoints()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool erf_sbm::try_two_moment_to_endpoints ( const amrex::Real  C,
const amrex::Real  M,
const amrex::Real  lower,
const amrex::Real  upper,
EndpointTransform &  result 
)
noexcept

Allocation-free canonical forward transform used by host and device code. Returns false instead of throwing when the state is invalid or materially unrealizable. The throwing host API below delegates to this function.

51 {
52  if (!amrex::Math::isfinite(C) || !amrex::Math::isfinite(M) || !amrex::Math::isfinite(lower) ||
53  !amrex::Math::isfinite(upper) || lower < amrex::Real(0.0) ||
54  !(lower < upper) || C < amrex::Real(0.0)) {
55  return false;
56  }
57  const amrex::Real scale = std::abs(M) + std::abs(lower * C) + std::abs(upper * C);
58  const amrex::Real moment_tolerance = amrex::Real(128.0) *
60  const amrex::Real low_numerator = std::fma(upper, C, -M);
61  const amrex::Real high_numerator = std::fma(-lower, C, M);
62  if (!amrex::Math::isfinite(scale) || !amrex::Math::isfinite(moment_tolerance) ||
63  !amrex::Math::isfinite(low_numerator) || !amrex::Math::isfinite(high_numerator) ||
64  low_numerator < -moment_tolerance || high_numerator < -moment_tolerance) {
65  return false;
66  }
67  if (C == amrex::Real(0.0)) {
68  if (std::abs(M) > moment_tolerance) return false;
69  result = {amrex::Real(0.0), amrex::Real(0.0), moment_tolerance,
70  moment_tolerance / (upper - lower), M != amrex::Real(0.0)};
71  return amrex::Math::isfinite(result.endpoint_tolerance);
72  }
73  const amrex::Real denominator = upper - lower;
74  amrex::Real L = low_numerator / denominator;
75  amrex::Real H = high_numerator / denominator;
76  const amrex::Real endpoint_tolerance = moment_tolerance / denominator;
77  if (!amrex::Math::isfinite(L) || !amrex::Math::isfinite(H) || !amrex::Math::isfinite(endpoint_tolerance)) {
78  return false;
79  }
80  const bool normalized = L < amrex::Real(0.0) || H < amrex::Real(0.0);
81  if (L < amrex::Real(0.0)) L = amrex::Real(0.0);
82  if (H < amrex::Real(0.0)) H = amrex::Real(0.0);
83  result = {L, H, moment_tolerance, endpoint_tolerance, normalized};
84  return true;
85 }

Referenced by erf_sbm::SBMTransport::advance_stage(), reconstruct_bin_core(), erf_sbm::SpectralGrid::two_moment_realizable(), and erf_sbm::SpectralGrid::two_moment_to_endpoints().

Here is the caller graph for this function:

◆ valid_population_remap_context()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool erf_sbm::valid_population_remap_context ( const PopulationRemapView &  population)
noexcept

Constant-time structural/context checks for already qualified immutable metadata. Callers must derive views from a validated SBMLayout or call the full validator below once before using a raw view in the remapping cores.

54 {
55  if (population.population_id < 0 ||
56  population.coordinate_kind != CoordinateKind::Mass ||
57  (population.moment_mode != MomentMode::OneMoment &&
58  population.moment_mode != MomentMode::TwoMoment) ||
59  population.nbins <= 0 || population.edges == nullptr ||
60  population.state_components < population.nbins || population.mass_offset < 0 ||
61  population.property_count < 0 ||
62  population.property_count > population.state_components / population.nbins ||
63  (population.property_count > 0 && population.property_component_offsets == nullptr)) {
64  return false;
65  }
66  if (population.mass_offset > population.state_components - population.nbins) return false;
67  if (population.moment_mode == MomentMode::OneMoment) {
68  if (population.number_offset != -1 || population.pivots == nullptr) return false;
69  } else if (population.number_offset < 0 ||
70  population.number_offset > population.state_components - population.nbins) {
71  return false;
72  }
73  if (population.moment_mode == MomentMode::TwoMoment &&
74  population.number_offset < population.mass_offset + population.nbins &&
75  population.mass_offset < population.number_offset + population.nbins) {
76  return false;
77  }
78  return true;
79 }

Referenced by apply_packet_routing_core(), erf_sbm::remap_detail::canonical_one_moment_bin_state_view(), erf_sbm::remap_detail::canonical_two_moment_bin_state_view(), erf_sbm::remap_detail::canonical_two_moment_moments(), plan_packet_routing(), population_remap_view(), project_reconstruction_bin_core(), reconstruct_bin_core(), erf_sbm::remap_detail::route_plan_is_consistent(), and valid_population_remap_view().

Here is the caller graph for this function:

◆ valid_population_remap_view()

AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool erf_sbm::valid_population_remap_view ( const PopulationRemapView &  population)
noexcept

Full O(nbins + properties^2) validator for untrusted/arbitrary views.

84 {
85  if (!valid_population_remap_context(population)) return false;
86  for (int edge = 0; edge <= population.nbins; ++edge) {
87  const amrex::Real value = population.edges[edge];
88  if (!amrex::Math::isfinite(value) || value < amrex::Real(0.0) ||
89  (edge > 0 && !(population.edges[edge - 1] < value))) {
90  return false;
91  }
92  }
93  if (population.moment_mode == MomentMode::OneMoment) {
94  for (int bin = 0; bin < population.nbins; ++bin) {
95  const amrex::Real pivot = population.pivots[bin];
96  if (!amrex::Math::isfinite(pivot) || pivot <= amrex::Real(0.0) ||
97  pivot < population.edges[bin] || pivot > population.edges[bin + 1] ||
98  (bin > 0 && !(population.pivots[bin - 1] < pivot))) {
99  return false;
100  }
101  }
102  }
103  for (int property = 0; property < population.property_count; ++property) {
104  const int offset = population.property_component_offsets[property];
105  if (offset < 0 || offset > population.state_components - population.nbins ||
106  (offset < population.mass_offset + population.nbins &&
107  population.mass_offset < offset + population.nbins)) {
108  return false;
109  }
110  if (population.moment_mode == MomentMode::TwoMoment &&
111  offset < population.number_offset + population.nbins &&
112  population.number_offset < offset + population.nbins) {
113  return false;
114  }
115  for (int previous = 0; previous < property; ++previous) {
116  const int previous_offset = population.property_component_offsets[previous];
117  if (offset < previous_offset + population.nbins &&
118  previous_offset < offset + population.nbins) {
119  return false;
120  }
121  }
122  }
123  return true;
124 }
AMREX_FORCE_INLINE IntVect offset(const int face_dir, const int normal)
Definition: ERF_ReadBndryPlanes.cpp:32
Here is the call graph for this function:

◆ validate_fixture_initial_state_canonicality()

FixtureInitialStateCanonicality erf_sbm::validate_fixture_initial_state_canonicality ( const SBMLayout &  layout,
const std::vector< amrex::Real > &  state 
)
inlinenoexcept

Apply the persisted-state admission rule to each bin before ERF accepts a user/fixture initial spectrum. Kept as the constructor's shared validation seam so unit tests can exercise the exact candidate-layout check.

26 {
27  FixtureInitialStateCanonicality result;
28  if (state.size() != static_cast<std::size_t>(layout.ncomp())) return result;
29 
30  result.canonical = true;
31  const int state_count = static_cast<int>(state.size());
32  for (const auto& population : layout.populations()) {
33  const auto view = population_remap_view(layout, population.population_id);
34  for (int bin = 0; bin < population.grid.nbins(); ++bin) {
36  view, bin, state.data(), state_count)) {
37  result.canonical = false;
38  result.population_id = population.population_id;
39  result.bin = bin;
40  result.moment_mode = population.moment_mode;
41  return result;
42  }
43  }
44  }
45  return result;
46 }

Referenced by ERF::ERF_shared().

Here is the call graph for this function:
Here is the caller graph for this function: