326 lines
17 KiB
C++
326 lines
17 KiB
C++
#include "analysis/hht_field_convergence.h"
|
|
#include <gtest/gtest.h>
|
|
#include <cmath>
|
|
#include <limits>
|
|
namespace fesa::hht_field_convergence {
|
|
namespace {
|
|
using Inputs = std::array<FieldInput, 2>;
|
|
using Histories = std::array<History, 2>;
|
|
Inputs Normal() {
|
|
Inputs f{}; f[0].active = true; f[0].flux = {{2.0, 0.0}};
|
|
f[0].residual = .01; f[0].increment = 1; f[0].correction = .01;
|
|
return f;
|
|
}
|
|
TEST(HhtFieldConvergence, SignedElementTotalAndSourcePopulationOracle) {
|
|
auto f = Normal(); f[0].flux.assign(30, {});
|
|
f[0].flux[0] = {8, -6}; f[0].flux[1] = {-2, 0};
|
|
f[0].flux[24] = {3, 0}; f[0].flux[27] = {-3, 0};
|
|
Histories h{}; h[0].maximum_sum = 10; h[0].maximum_count = 1;
|
|
auto r = Evaluate(f, h, 1, 2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].spatial_flux, 2.5);
|
|
r = Evaluate(f, {}, 1, 2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].spatial_flux, 1.0/3.0);
|
|
}
|
|
TEST(HhtFieldConvergence, NormalBoundaryAndInitialCorrectionMissing) {
|
|
auto f = Normal(); auto r = Evaluate(f, {}, 1, 2);
|
|
ASSERT_TRUE(r.status.IsOk()); EXPECT_TRUE(r.field_pass);
|
|
f[0].residual = std::nextafter(.005 * 2, INFINITY);
|
|
r = Evaluate(f, {}, 1, 2); ASSERT_TRUE(r.status.IsOk()); EXPECT_FALSE(r.field_pass);
|
|
f = Normal(); r = Evaluate(f, {}, 0, 2);
|
|
ASSERT_TRUE(r.status.IsOk()); EXPECT_FALSE(r.field_pass);
|
|
EXPECT_FALSE(r.fields[0].correction_pass.has_value());
|
|
}
|
|
TEST(HhtFieldConvergence, ScalarPopulationThresholdAndReactivation) {
|
|
auto f=Normal(); Histories h{}; h[0].maximum_sum=100; h[0].maximum_count=1;
|
|
f[0].flux={{0,0},{.001,0},{10,0}};
|
|
auto r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].spatial_flux,5.0005);
|
|
f[0].flux[2].internal=std::nextafter(10.,0.);
|
|
r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].spatial_flux,(.001+std::nextafter(10.,0.))/3);
|
|
f[0].flux={{0,0},{0,0}}; r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].spatial_flux,0);
|
|
}
|
|
TEST(HhtFieldConvergence, InitialAverageAndNonzeroEligibilityBoundary) {
|
|
auto f=Normal(); const double edge=1e-5*.01;
|
|
for (double x:{std::nextafter(edge,0.),edge,std::nextafter(edge,INFINITY)}) {
|
|
f[0].flux={{x,0}}; auto r=Evaluate(f,{},0,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].time_average_flux,x>edge?x:.01);
|
|
EXPECT_EQ(r.pending[0].nonzero_count,x>edge?1U:0U);
|
|
EXPECT_EQ(r.pending[0].maximum_count,1U);
|
|
}
|
|
}
|
|
TEST(HhtFieldConvergence, PendingHistoryIsRecomputedAndRollbackIsPure) {
|
|
auto f=Normal(); Histories h{}; h[0]={10,1,4,1};
|
|
f[0].flux={{30,0}}; auto a=Evaluate(f,h,1,2); ASSERT_TRUE(a.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*a.fields[0].time_average_flux,20);
|
|
f[0].flux={{70,0}}; auto b=Evaluate(f,h,2,2); ASSERT_TRUE(b.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*b.fields[0].time_average_flux,40);
|
|
EXPECT_DOUBLE_EQ(b.pending[0].nonzero_sum,80);
|
|
EXPECT_DOUBLE_EQ(b.pending[0].maximum_sum,74);
|
|
EXPECT_EQ(b.pending[0].maximum_count,2U);
|
|
EXPECT_DOUBLE_EQ(h[0].nonzero_sum,10); EXPECT_EQ(h[0].nonzero_count,1U);
|
|
f[0].flux={{0,0}}; auto z=Evaluate(f,h,1,2); ASSERT_TRUE(z.status.IsOk());
|
|
EXPECT_EQ(z.pending[0].nonzero_count,1U); EXPECT_EQ(z.pending[0].maximum_count,2U);
|
|
EXPECT_DOUBLE_EQ(z.pending[0].maximum_sum,4);
|
|
}
|
|
TEST(HhtFieldConvergence, RawScaleConversionIsSimultaneousAndUnitConsistent) {
|
|
auto f=Normal(); Histories h{}; h[0]={3,1,3,1}; h[1]={.3,1,.3,1};
|
|
f[0].flux={{3,0}}; f[1].active=true; f[1].flux={{.1,0}};
|
|
auto r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].residual_scale,3);
|
|
EXPECT_DOUBLE_EQ(*r.fields[1].time_average_flux,.2);
|
|
EXPECT_DOUBLE_EQ(*r.fields[1].residual_scale,6);
|
|
f[1].active=false; r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.fields[1].branch,Branch::kNotEvaluated);
|
|
EXPECT_FALSE(r.fields[1].field_pass.has_value());
|
|
}
|
|
TEST(HhtFieldConvergence, EstimatedCorrectionHasRealHistoryAndNormalOnly) {
|
|
auto f=Normal(); f[0].flux={{400,0}}; f[0].residual=2;
|
|
f[0].correction=.04; f[0].previous_residual=4; f[0].older_residual=8;
|
|
auto r=Evaluate(f,{},2,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].estimated_correction,.02); EXPECT_FALSE(r.field_pass);
|
|
f[0].residual=1; r=Evaluate(f,{},2,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].estimated_correction,.01); EXPECT_TRUE(r.field_pass);
|
|
f[0].older_residual=0; r=Evaluate(f,{},2,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_FALSE(r.fields[0].estimated_correction.has_value()); EXPECT_FALSE(r.field_pass);
|
|
f[0].older_residual=8; r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_FALSE(r.fields[0].estimated_correction.has_value()); EXPECT_FALSE(r.field_pass);
|
|
}
|
|
TEST(HhtFieldConvergence, NonquadraticIterationAndRateBoundaries) {
|
|
auto f=Normal(); f[0].flux={{100,0}}; f[0].residual=2;
|
|
f[0].previous_residual=4; f[0].older_residual=8;
|
|
auto r=Evaluate(f,{},9,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.fields[0].branch,Branch::kNormal); EXPECT_FALSE(r.field_pass);
|
|
r=Evaluate(f,{},10,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.fields[0].branch,Branch::kNonquadratic); EXPECT_TRUE(r.field_pass);
|
|
EXPECT_FALSE(r.fields[0].estimated_correction.has_value());
|
|
f[0].older_residual=16; f[0].residual=1;
|
|
r=Evaluate(f,{},10,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_EQ(r.fields[0].branch,Branch::kNonquadratic);
|
|
f[0].residual=.5; r=Evaluate(f,{},10,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.fields[0].branch,Branch::kNormal);
|
|
f[0].residual=4; r=Evaluate(f,{},10,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.fields[0].branch,Branch::kNormal);
|
|
}
|
|
TEST(HhtFieldConvergence, ZeroFluxUsesResidualOrActualCorrectionAtBoundary) {
|
|
auto f=Normal(); Histories h{}; h[0]={2,1,2,1};
|
|
f[0].flux={{0,0}}; f[0].residual=1e-5*2; f[0].correction.reset();
|
|
auto r=Evaluate(f,h,0,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_TRUE(r.field_pass);
|
|
EXPECT_EQ(r.fields[0].branch,Branch::kZeroFlux); EXPECT_FALSE(r.fields[0].correction_pass.has_value());
|
|
f[0].residual=std::nextafter(1e-5*2,INFINITY);
|
|
r=Evaluate(f,h,0,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_FALSE(r.field_pass);
|
|
f[0].correction=.001; r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_TRUE(r.field_pass);
|
|
f[0].correction=std::nextafter(.001,INFINITY);
|
|
r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_FALSE(r.field_pass);
|
|
f[0].increment=0; f[0].correction=0;
|
|
r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_TRUE(r.field_pass);
|
|
}
|
|
TEST(HhtFieldConvergence, LinearRequiresEveryActiveFieldAndAnActualIteration) {
|
|
auto f=Normal(); f[1]=f[0]; f[1].flux={{4,0}};
|
|
f[0].residual=2e-8; f[1].residual=4e-8; f[0].correction.reset(); f[1].correction.reset();
|
|
auto r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_TRUE(r.field_pass);
|
|
EXPECT_EQ(r.fields[0].branch,Branch::kLinear); EXPECT_EQ(r.fields[1].branch,Branch::kLinear);
|
|
f[1].residual=std::nextafter(4e-8,INFINITY);
|
|
r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_FALSE(r.field_pass);
|
|
EXPECT_EQ(r.fields[0].branch,Branch::kNormal);
|
|
f[1].residual=4e-8; r=Evaluate(f,{},0,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_FALSE(r.field_pass);
|
|
}
|
|
TEST(HhtFieldConvergence, SmallTranslationExemptionIsStrictAndNeverRotational) {
|
|
auto f=Normal(); f[0].increment=std::nextafter(2e-8,0.); f[0].correction=1;
|
|
auto r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_TRUE(r.field_pass);
|
|
EXPECT_FALSE(r.fields[0].correction_pass.has_value());
|
|
f[0].increment=2e-8; r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_FALSE(r.field_pass);
|
|
f[1]=f[0]; f[0].active=false; f[1].increment=0; f[1].correction=0;
|
|
r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_TRUE(r.field_pass);
|
|
f[1].correction=std::numeric_limits<double>::denorm_min();
|
|
r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk()); EXPECT_FALSE(r.field_pass);
|
|
}
|
|
TEST(HhtFieldConvergence, InvalidAndOverflowNeverBecomeMissingOrPassing) {
|
|
const double inf=std::numeric_limits<double>::infinity();
|
|
const double nan=std::numeric_limits<double>::quiet_NaN();
|
|
const double max=std::numeric_limits<double>::max();
|
|
for(double x:{0.,-1.,inf,nan}) EXPECT_FALSE(Evaluate(Normal(),{},1,x).status.IsOk());
|
|
for(double x:{-1.,inf,nan}) {
|
|
auto f=Normal(); f[0].residual=x; EXPECT_FALSE(Evaluate(f,{},1,2).status.IsOk());
|
|
f=Normal(); f[0].correction=x; EXPECT_FALSE(Evaluate(f,{},1,2).status.IsOk());
|
|
f=Normal(); f[0].older_residual=x; EXPECT_FALSE(Evaluate(f,{},2,2).status.IsOk());
|
|
f=Normal(); f[0].flux={{x,0}};
|
|
if(x!=-1.) EXPECT_FALSE(Evaluate(f,{},1,2).status.IsOk());
|
|
}
|
|
auto f=Normal(); f[0].flux.clear(); EXPECT_FALSE(Evaluate(f,{},1,2).status.IsOk());
|
|
f=Normal(); f[0].flux={{max,max}}; EXPECT_FALSE(Evaluate(f,{},1,2).status.IsOk());
|
|
f=Normal(); f[0].flux={{max,0},{max,0}}; EXPECT_FALSE(Evaluate(f,{},1,2).status.IsOk());
|
|
f=Normal(); f[0].older_residual=std::numeric_limits<double>::denorm_min();
|
|
f[0].previous_residual=1; EXPECT_FALSE(Evaluate(f,{},2,2).status.IsOk());
|
|
Histories h{}; h[0]={max,1,max,1}; f=Normal(); f[0].flux={{max,0}}; EXPECT_FALSE(Evaluate(f,h,1,2).status.IsOk());
|
|
h={}; h[0].maximum_count=std::numeric_limits<std::uint64_t>::max();
|
|
EXPECT_FALSE(Evaluate(Normal(),h,1,2).status.IsOk());
|
|
h={}; h[0]={0,1,0,1}; f=Normal(); f[0].flux={{0,0}};
|
|
EXPECT_FALSE(Evaluate(f,h,1,2).status.IsOk());
|
|
f=Normal(); f[0].residual=1e200; f[0].correction=1e200;
|
|
f[0].older_residual=1; f[0].previous_residual=1;
|
|
const auto overflow=Evaluate(f,{},2,2);
|
|
EXPECT_FALSE(overflow.status.IsOk()); EXPECT_FALSE(overflow.field_pass);
|
|
f=Normal(); f[1]=f[0]; f[0].flux={{max,0}};
|
|
EXPECT_FALSE(Evaluate(f,{},1,2).status.IsOk());
|
|
Inputs inactive{}; auto r=Evaluate(inactive,{},0,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_TRUE(r.field_pass); EXPECT_FALSE(r.fields[0].field_pass.has_value());
|
|
}
|
|
TEST(HhtFieldConvergence, DecisionThresholdsBelowEqualAboveWithoutTolerance) {
|
|
auto f=Normal();
|
|
for(double x:{std::nextafter(.01,0.),.01,std::nextafter(.01,INFINITY)}) {
|
|
f[0].correction=x; auto r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.field_pass,x<=.01);
|
|
}
|
|
for(double x:{std::nextafter(.01,0.),.01,std::nextafter(.01,INFINITY)}) {
|
|
f=Normal(); f[0].residual=x; auto r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.field_pass,x<=.01);
|
|
}
|
|
for(double x:{std::nextafter(2.,0.),2.,std::nextafter(2.,INFINITY)}) {
|
|
f=Normal(); f[0].flux={{100,0}}; f[0].residual=x;
|
|
f[0].previous_residual=4; f[0].older_residual=8;
|
|
auto r=Evaluate(f,{},10,2); ASSERT_TRUE(r.status.IsOk());
|
|
// For below 2 the detector selects normal; equality/above selects Rp.
|
|
EXPECT_EQ(r.field_pass,x==2.);
|
|
}
|
|
}
|
|
TEST(HhtFieldConvergence, MixedIncidenceRetainsEveryElementScalarOccurrence) {
|
|
auto f=Normal(); f[0].flux={{6,0},{-2,0},{-1,0},{9,0},{0,0}};
|
|
// Three occurrences can share one global DOF and one belong to another.
|
|
// Node averaging would produce 6, signed assembly would produce 3 and 9.
|
|
auto r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].spatial_flux,3.6);
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].maximum_flux,9);
|
|
}
|
|
|
|
TEST(HhtFieldConvergence, RemainingThresholdsHaveAdjacentRepresentableBoundaries) {
|
|
for (double x:{std::nextafter(.001,0.),.001,std::nextafter(.001,INFINITY)}) {
|
|
auto f=Normal(); Histories h{}; h[0]={2,1,2,1};
|
|
f[0].flux={{0,0}}; f[0].residual=.1; f[0].correction=x;
|
|
auto r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.field_pass,x<=.001);
|
|
}
|
|
for (double x:{std::nextafter(2e-5,0.),2e-5,std::nextafter(2e-5,INFINITY)}) {
|
|
auto f=Normal(); Histories h{}; h[0]={2,1,2,1};
|
|
f[0].flux={{0,0}}; f[0].residual=x; f[0].correction.reset();
|
|
auto r=Evaluate(f,h,0,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.field_pass,x<=2e-5);
|
|
}
|
|
for (double x:{std::nextafter(2e-8,0.),2e-8,std::nextafter(2e-8,INFINITY)}) {
|
|
auto f=Normal(); f[0].increment=x; f[0].correction=1;
|
|
auto r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.field_pass,x<2e-8);
|
|
f=Normal(); f[0].residual=x; f[0].correction.reset();
|
|
r=Evaluate(f,{},1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.fields[0].branch,x<=2e-8?Branch::kLinear:Branch::kNormal);
|
|
}
|
|
for (double x:{std::nextafter(.001,0.),.001,std::nextafter(.001,INFINITY)}) {
|
|
auto f=Normal(); Histories h{}; h[0]={0,0,100,1};
|
|
f[0].flux={{0,0},{x,0},{10,0}};
|
|
auto r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_DOUBLE_EQ(*r.fields[0].spatial_flux,x<.001?10:(10+x)/2);
|
|
}
|
|
for (double x:{std::nextafter(2.,0.),2.,std::nextafter(2.,INFINITY)}) {
|
|
auto f=Normal(); f[0].flux={{100,0}}; f[0].residual=x;
|
|
f[0].previous_residual=4; f[0].older_residual=16;
|
|
auto r=Evaluate(f,{},10,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.fields[0].branch,Branch::kNonquadratic);
|
|
EXPECT_EQ(r.field_pass,x<=2);
|
|
}
|
|
}
|
|
TEST(HhtFieldConvergence, CurrentIncludedEligibilityBoundaryPreservesPendingNonzeroHistory) {
|
|
Histories h{}; h[0]={199999,1,199999,1};
|
|
// 199999+1 rounds exactly to 200000; current-included mean is 100000.
|
|
// Its epsilon threshold is exactly 1, while the old-base threshold is 1.99999.
|
|
for (double b:{std::nextafter(1.,0.),1.,std::nextafter(1.,INFINITY)}) {
|
|
auto f=Normal(); f[0].flux={{b,0}};
|
|
const auto r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(*r.fields[0].spatial_flux,b);
|
|
EXPECT_EQ(*r.fields[0].time_average_flux,b>1?100000:199999);
|
|
EXPECT_EQ(r.pending[0].nonzero_sum,b>1?200000:199999);
|
|
EXPECT_EQ(r.pending[0].nonzero_count,b>1?2U:1U);
|
|
EXPECT_EQ(h[0].nonzero_sum,199999);
|
|
EXPECT_EQ(h[0].nonzero_count,1U);
|
|
}
|
|
auto f=Normal(); f[0].flux={{1.5,0}};
|
|
const auto r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(*r.fields[0].time_average_flux,100000.25);
|
|
EXPECT_EQ(r.pending[0].nonzero_sum,200000.5);
|
|
EXPECT_EQ(r.pending[0].nonzero_count,2U);
|
|
}
|
|
TEST(HhtFieldConvergence, ConversionSpatialPrerequisiteHasAdjacentBoundaries) {
|
|
Histories h{}; h[0]={1023,1023,1023,1023}; h[1]={4,1,4,1};
|
|
// Adding any adjacent-to-one b to 1023 rounds to 1024: raw force stays 1.
|
|
// Peer raw moment stays 4 and L=1 makes the candidate force scale exactly 4.
|
|
for (double b:{std::nextafter(1.,0.),1.,std::nextafter(1.,INFINITY)}) {
|
|
auto f=Normal(); f[0].flux={{b,0}}; f[1]=f[0]; f[1].flux={{4,0}};
|
|
const auto r=Evaluate(f,h,1,1); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(*r.fields[0].spatial_flux,b);
|
|
EXPECT_EQ(*r.fields[0].time_average_flux,1);
|
|
EXPECT_EQ(*r.fields[1].time_average_flux,4);
|
|
EXPECT_EQ(*r.fields[0].residual_scale,b<=1?4:1);
|
|
EXPECT_EQ(r.pending[0].nonzero_sum,1024);
|
|
EXPECT_EQ(r.pending[0].nonzero_count,1024U);
|
|
EXPECT_EQ(r.pending[1].nonzero_sum,8);
|
|
EXPECT_EQ(r.pending[1].nonzero_count,2U);
|
|
EXPECT_EQ(h[0].nonzero_sum,1023);
|
|
EXPECT_EQ(h[0].nonzero_count,1023U);
|
|
EXPECT_EQ(h[1].nonzero_sum,4);
|
|
}
|
|
}
|
|
TEST(HhtFieldConvergence, ConversionRawVersusConvertedScaleHasAdjacentBoundaries) {
|
|
Histories h{}; h[0]={1,1,1,1}; h[1]=h[0];
|
|
// Zero samples preserve both raw means at 1. Only L changes convF=1/L.
|
|
for (double length:{std::nextafter(1.,0.),1.,std::nextafter(1.,INFINITY)}) {
|
|
auto f=Normal(); f[0].flux={{0,0}}; f[1]=f[0];
|
|
const auto r=Evaluate(f,h,1,length); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(*r.fields[0].time_average_flux,1);
|
|
EXPECT_EQ(*r.fields[1].time_average_flux,1);
|
|
// Reciprocating the adjacent value below 1 rounds to the adjacent value above 1.
|
|
EXPECT_EQ(*r.fields[0].residual_scale,
|
|
length<1?std::nextafter(1.,INFINITY):1);
|
|
for (std::size_t a=0; a<2; ++a) {
|
|
EXPECT_EQ(r.pending[a].nonzero_sum,1);
|
|
EXPECT_EQ(r.pending[a].nonzero_count,1U);
|
|
EXPECT_EQ(h[a].nonzero_sum,1);
|
|
EXPECT_EQ(h[a].nonzero_count,1U);
|
|
}
|
|
}
|
|
}
|
|
TEST(HhtFieldConvergence, ZeroFluxClassificationTestsSpatialFluxBoundary) {
|
|
// A peer raw mean of 1 converts the small force field to a fixed scale of 1.
|
|
// Vary bbar, not rho, across epsilon*scale; rho=.1 prevents the linear exemption.
|
|
for (double b:{std::nextafter(1e-5,0.),1e-5,std::nextafter(1e-5,INFINITY)}) {
|
|
auto f=Normal(); f[0].flux={{b,0}}; f[0].residual=.1;
|
|
f[1]=f[0]; f[1].flux={{1,0}};
|
|
const auto r=Evaluate(f,{},1,1); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(*r.fields[0].spatial_flux,b);
|
|
EXPECT_EQ(*r.fields[0].residual_scale,1);
|
|
EXPECT_EQ(r.fields[0].branch,b<=1e-5?Branch::kZeroFlux:Branch::kNormal);
|
|
}
|
|
}
|
|
TEST(HhtFieldConvergence, EstimatedCorrectionAdjacentBoundariesDriveNormalCorrectionPass) {
|
|
for (double estimate:{std::nextafter(.01,0.),.01,std::nextafter(.01,INFINITY)}) {
|
|
auto f=Normal(); f[0].flux={{400,0}}; f[0].residual=1;
|
|
// Prior residual minimum is 4; power-of-two scaling preserves adjacent bits.
|
|
// The actual correction exceeds .01 in all three cases, isolating the estimate.
|
|
f[0].correction=4*estimate; f[0].previous_residual=4; f[0].older_residual=8;
|
|
const auto r=Evaluate(f,{},2,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(r.fields[0].branch,Branch::kNormal);
|
|
ASSERT_GT(*f[0].correction,.01);
|
|
EXPECT_EQ(*r.fields[0].estimated_correction,estimate);
|
|
EXPECT_EQ(*r.fields[0].correction_pass,estimate<=.01);
|
|
EXPECT_EQ(r.field_pass,estimate<=.01);
|
|
}
|
|
}
|
|
TEST(HhtFieldConvergence, PopulationReactivationThresholdImmediatelyAboveUsesFilteredPopulation) {
|
|
auto f=Normal(); Histories h{}; h[0]={0,0,100,1};
|
|
const double maximum=std::nextafter(10.,INFINITY);
|
|
f[0].flux={{0,0},{.001,0},{maximum,0}};
|
|
const auto r=Evaluate(f,h,1,2); ASSERT_TRUE(r.status.IsOk());
|
|
EXPECT_EQ(*r.fields[0].maximum_flux,maximum);
|
|
EXPECT_EQ(*r.fields[0].spatial_flux,(10.000000000000002+.001)/2);
|
|
}
|
|
} // namespace
|
|
} // namespace fesa::hht_field_convergence
|