-
Notifications
You must be signed in to change notification settings - Fork 106
fix: CSV output in coupled simulation #4009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
3cf71e3
8d1bfb5
80caef7
109a70e
ec00de0
8f03857
d87d4a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |
| #include "SolverStatistics.hpp" | ||
|
|
||
| #include "fileIO/Outputs/OutputBase.hpp" | ||
| #include "common/MpiWrapper.hpp" | ||
|
|
||
| namespace geos | ||
| { | ||
|
|
@@ -145,6 +146,13 @@ void IterationsStatistics::writeIterationStatsToTable() | |
| if( m_numTimeSteps == 0 || !m_logOutputRequest ) | ||
| return; | ||
|
|
||
| real64 const setupTime = m_setupTime; | ||
| real64 const solveTime = m_solveTime; | ||
| resetSolverLinearTime(); | ||
|
|
||
| if( !m_CSVOutputRequest || MpiWrapper::commRank() != 0 ) | ||
| return; | ||
|
|
||
| m_iterationData.addRow( m_numTimeSteps, | ||
| m_numTimeStepCuts, | ||
| m_numSuccessfulConfigIterations, | ||
|
|
@@ -153,24 +161,23 @@ void IterationsStatistics::writeIterationStatsToTable() | |
| m_numDiscardedConfigIterations, | ||
| m_numDiscardedNonlinearIterations, | ||
| m_numDiscardedLinearIterations, | ||
| m_setupTime, | ||
| m_solveTime ); | ||
| setupTime, | ||
| solveTime ); | ||
|
|
||
| if( !m_CSVOutputOpened ) | ||
| { | ||
| m_logStream.open( m_iterationsFilename ); | ||
| m_iterationCSVFormatter = std::make_unique< TableCSVFormatter >( m_iterationCSVLayout ); | ||
| m_logStream << m_iterationCSVFormatter->headerToString( ); | ||
| m_CSVOutputOpened = true; | ||
| m_CSVOutputOpened = true; | ||
|
Comment on lines
171
to
+172
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A bit of renaming could clarify: |
||
| } | ||
|
|
||
| m_logStream << m_iterationCSVFormatter->dataToString( m_iterationData ); | ||
| m_logStream.flush(); | ||
| m_iterationData.clear(); | ||
|
|
||
| resetSolverLinearTime(); | ||
| } | ||
|
|
||
|
|
||
| void IterationsStatistics::outputStatistics() const | ||
| { | ||
| // no statistics to output when no time steps have been recorded | ||
|
|
@@ -228,23 +235,27 @@ void ConvergenceStatistics::writeConvergenceStatsToTable() | |
|
|
||
| m_convergenceData.addRow( residualsNormCells ); | ||
|
|
||
| if( !m_CSVOutputOpened ) | ||
| if( MpiWrapper::commRank() == 0 ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should it apply to the whole |
||
| { | ||
| string_array header = {"Cycle number", "time_n (s)", "dt (s)", "iteration"}; | ||
| for( auto const & residual : m_residuals ) | ||
| if( !m_CSVOutputOpened ) | ||
| { | ||
| header.emplace_back( residual.first ); | ||
| string_array header = {"Cycle number", "time_n (s)", "dt (s)", "iteration"}; | ||
| for( auto const & residual : m_residuals ) | ||
| { | ||
| header.emplace_back( residual.first ); | ||
| } | ||
| m_convergenceLayout.addColumns( header ); | ||
|
|
||
| m_logStream.open( m_convergenceFilename ); | ||
| m_convergenceFormatter = std::make_unique< TableCSVFormatter >( m_convergenceLayout ); | ||
| m_logStream << m_convergenceFormatter->headerToString( ); | ||
| m_CSVOutputOpened = true; | ||
| } | ||
| m_convergenceLayout.addColumns( header ); | ||
|
|
||
| m_logStream.open( m_convergenceFilename ); | ||
| m_convergenceFormatter = std::make_unique< TableCSVFormatter >( m_convergenceLayout ); | ||
| m_logStream << m_convergenceFormatter->headerToString( ); | ||
| m_CSVOutputOpened = true; | ||
| m_logStream << m_convergenceFormatter->dataToString( m_convergenceData ); | ||
| m_logStream.flush(); | ||
| } | ||
|
|
||
| m_logStream << m_convergenceFormatter->dataToString( m_convergenceData ); | ||
| m_logStream.flush(); | ||
| m_convergenceData.clear(); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| set( multiphysics_gtest_tests | ||
| testCoupledSolverStatisticsCSV.cpp ) | ||
|
|
||
| set( dependencyList mainInterface testingUtilities gtest ${parallelDeps} ) | ||
|
|
||
| blt_add_executable( NAME testCoupledSolverStatisticsCSV | ||
| SOURCES testCoupledSolverStatisticsCSV.cpp | ||
| OUTPUT_DIR ${TEST_OUTPUT_DIRECTORY} | ||
| DEPENDS_ON ${dependencyList} | ||
| FOLDER UnitTests ) | ||
|
|
||
| geos_add_test( NAME testCoupledSolverStatisticsCSV | ||
| COMMAND testCoupledSolverStatisticsCSV ) | ||
|
|
||
| if( ENABLE_MPI ) | ||
| geos_add_test( NAME testCoupledSolverStatisticsCSV_2ranks | ||
| COMMAND testCoupledSolverStatisticsCSV | ||
| NUM_MPI_TASKS 2 ) | ||
| endif() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,237 @@ | ||
| #include "integrationTests/fluidFlowTests/testCompFlowUtils.hpp" | ||
| #include "integrationTests/testingUtilities/TestingTasks.hpp" | ||
| #include "mainInterface/initialization.hpp" | ||
| #include "mainInterface/GeosxState.hpp" | ||
| #include "fileIO/Outputs/OutputBase.hpp" | ||
| #include "common/MpiWrapper.hpp" | ||
| #include "common/Path.hpp" | ||
|
|
||
| #include <gtest/gtest.h> | ||
| #include <fstream> | ||
|
|
||
| using namespace geos; | ||
| using namespace geos::dataRepository; | ||
| using namespace geos::testing; | ||
|
|
||
| CommandLineOptions g_commandLineOptions; | ||
|
|
||
| //////////////////////////////// Utilities //////////////////////////////// | ||
|
|
||
| string replaceAll( string str, string_view token, string_view value ) | ||
| { | ||
| for( size_t pos = 0; ( pos = str.find( token, pos ) ) != string::npos; pos += value.size() ) | ||
| str.replace( pos, token.size(), value ); | ||
| return str; | ||
| } | ||
|
|
||
| struct CSVStats | ||
| { | ||
| bool exists = false; | ||
| integer headerCount = 0; | ||
| integer dataRowCount = 0; | ||
| }; | ||
|
|
||
| CSVStats inspectCSV( string const & path, string_view headerToken ) | ||
| { | ||
| CSVStats stats; | ||
| std::ifstream file( path ); | ||
| if( !file.is_open() ) | ||
| return stats; | ||
| stats.exists = true; | ||
| string line; | ||
| while( std::getline( file, line ) ) | ||
| { | ||
| if( line.find( headerToken ) != string::npos ) | ||
| stats.headerCount++; | ||
| else if( !line.empty() ) | ||
| stats.dataRowCount++; | ||
| } | ||
| return stats; | ||
| } | ||
|
|
||
| string statsFilePath( string_view solverName, string_view suffix ) | ||
| { | ||
| return joinPath( OutputBase::getOutputDirectory(), | ||
| "convergence", | ||
| GEOS_FMT( "{}_{}.csv", solverName, suffix ) ); | ||
| } | ||
|
|
||
| //////////////////////////////// Input deck //////////////////////////////// | ||
|
|
||
| // Mandel-type consolidation problem (PoroElastic_Mandel_smoke), self-contained: | ||
| // initial-displacement table files removed, constant step load instead of loadFunction. | ||
| static char const * xmlTemplate = | ||
| R"xml( | ||
| <Problem> | ||
| <Solvers gravityVector="{ 0.0, 0.0, 0.0 }"> | ||
| <SinglePhasePoromechanics | ||
| name="poroSolve@SUFFIX@" | ||
| solidSolverName="lagsolve@SUFFIX@" | ||
| flowSolverName="flowSolve@SUFFIX@" | ||
| writeStatistics="@CSV_MODE@" | ||
| targetRegions="{ Domain }"> | ||
| <NonlinearSolverParameters | ||
| couplingType="@COUPLING_TYPE@" | ||
| lineSearchAction="@LINE_SEARCH_ACTION@" | ||
| newtonMaxIter="10" | ||
| newtonTol="1.0e-3" | ||
| maxTimeStepCuts="2"/> | ||
| <LinearSolverParameters directParallel="0"/> | ||
| </SinglePhasePoromechanics> | ||
|
|
||
| <SolidMechanicsLagrangianFEM | ||
| name="lagsolve@SUFFIX@" | ||
| discretization="FE1" | ||
| writeStatistics="@CSV_MODE@" | ||
| targetRegions="{ Domain }"> | ||
| <NonlinearSolverParameters newtonTol="1.0e-4" newtonMaxIter="40"/> | ||
| <LinearSolverParameters directParallel="0"/> | ||
| </SolidMechanicsLagrangianFEM> | ||
|
|
||
| <SinglePhaseFVM | ||
| name="flowSolve@SUFFIX@" | ||
| discretization="singlePhaseTPFA" | ||
| writeStatistics="@CSV_MODE@" | ||
| targetRegions="{ Domain }"> | ||
| <NonlinearSolverParameters newtonTol="1.0e-4" newtonMaxIter="40"/> | ||
| <LinearSolverParameters directParallel="0"/> | ||
| </SinglePhaseFVM> | ||
| </Solvers> | ||
|
|
||
| <Mesh> | ||
| <InternalMesh | ||
| name="mesh1" elementTypes="{ C3D8 }" | ||
| xCoords="{ 0.0, 1.0 }" yCoords="{ 0.0, 0.1 }" zCoords="{ 0.0, 1.0 }" | ||
| nx="{ 2 }" ny="{ 1 }" nz="{ 2 }" | ||
| cellBlockNames="{ cb1 }"/> | ||
| </Mesh> | ||
|
|
||
| <NumericalMethods> | ||
| <FiniteElements> | ||
| <FiniteElementSpace name="FE1" order="1"/> | ||
| </FiniteElements> | ||
| <FiniteVolume> | ||
| <TwoPointFluxApproximation name="singlePhaseTPFA"/> | ||
| </FiniteVolume> | ||
| </NumericalMethods> | ||
|
|
||
| <ElementRegions> | ||
| <CellElementRegion name="Domain" cellBlocks="{ * }" materialList="{ shale, water }"/> | ||
| </ElementRegions> | ||
|
|
||
| <Constitutive> | ||
| <PorousElasticIsotropic name="shale" solidModelName="shaleSolid" | ||
| porosityModelName="shalePorosity" permeabilityModelName="shalePerm"/> | ||
| <ElasticIsotropic name="shaleSolid" defaultDensity="0" | ||
| defaultBulkModulus="6.6667e7" defaultShearModulus="4.0e7"/> | ||
| <BiotPorosity name="shalePorosity" defaultGrainBulkModulus="1.0e27" | ||
| defaultReferencePorosity="0.375"/> | ||
| <ConstantPermeability name="shalePerm" | ||
| permeabilityComponents="{ 1.0e-12, 0.0, 1.0e-12 }"/> | ||
| <CompressibleSinglePhaseFluid name="water" defaultDensity="1000" | ||
| defaultViscosity="0.001" referencePressure="0.0" referenceDensity="1" | ||
| compressibility="4.4e-10" referenceViscosity="0.001" viscosibility="0.0"/> | ||
| </Constitutive> | ||
|
|
||
| <FieldSpecifications> | ||
| <FieldSpecification name="initialPressure" initialCondition="1" setNames="{ all }" | ||
| objectPath="ElementRegions/Domain/cb1" fieldName="pressure" scale="0.0"/> | ||
| <FieldSpecification name="xnegconstraint" objectPath="nodeManager" | ||
| fieldName="totalDisplacement" component="0" scale="0.0" setNames="{ xneg }"/> | ||
| <FieldSpecification name="yconstraint" objectPath="nodeManager" | ||
| fieldName="totalDisplacement" component="1" scale="0.0" setNames="{ yneg, ypos }"/> | ||
| <FieldSpecification name="zconstraint" objectPath="nodeManager" | ||
| fieldName="totalDisplacement" component="2" scale="0.0" setNames="{ zneg }"/> | ||
| <FieldSpecification name="normalDisplacement" objectPath="nodeManager" | ||
| fieldName="totalDisplacement" component="2" scale="-1.0e-4" setNames="{ zpos }"/> | ||
| <FieldSpecification name="boundaryPressure" objectPath="faceManager" | ||
| fieldName="pressure" scale="0.0" setNames="{ xpos }"/> | ||
| </FieldSpecifications> | ||
|
|
||
| <Events maxTime="0.05"> | ||
| <PeriodicEvent name="solverApplication" forceDt="0.025" | ||
| target="/Solvers/poroSolve@SUFFIX@"/> | ||
| </Events> | ||
| </Problem> | ||
| )xml"; | ||
|
|
||
| //////////////////////////////// Tests //////////////////////////////// | ||
|
|
||
| struct Params | ||
| { | ||
| string couplingType; // "FullyImplicit" or "Sequential" | ||
| string csvMode; // "all", "none", ... | ||
| string suffix; // unique per variant so output files do not collide | ||
| }; | ||
|
|
||
| // Run a single coupled-statistics scenario and verify its CSV output. | ||
| static void runScenario( Params const & p ) | ||
| { | ||
| SCOPED_TRACE( GEOS_FMT( "{}_{}", p.couplingType, p.csvMode ) ); | ||
|
|
||
| // Sequential coupling forbids line search; use "None" there. | ||
| string const lineSearchAction = ( p.couplingType == "Sequential" ) ? "None" : "Attempt"; | ||
|
|
||
| string xmlInput = replaceAll( xmlTemplate, "@COUPLING_TYPE@", p.couplingType ); | ||
| xmlInput = replaceAll( xmlInput, "@CSV_MODE@", p.csvMode ); | ||
| xmlInput = replaceAll( xmlInput, "@SUFFIX@", p.suffix ); | ||
| xmlInput = replaceAll( xmlInput, "@LINE_SEARCH_ACTION@", lineSearchAction ); | ||
|
|
||
| GeosxState state( std::make_unique< CommandLineOptions >( g_commandLineOptions ) ); | ||
| ProblemManager & problem = state.getProblemManager(); | ||
| setupProblemFromXML( problem, xmlInput.data() ); | ||
|
|
||
| EXPECT_FALSE( problem.runSimulation() ) << "Simulation exited early."; | ||
|
|
||
| MpiWrapper::barrier(); | ||
| if( MpiWrapper::commRank() != 0 ) | ||
| return; | ||
|
|
||
| string const coupledCsv = statsFilePath( "poroSolve" + p.suffix, "iterations" ); | ||
| string const flowCsv = statsFilePath( "flowSolve" + p.suffix, "iterations" ); | ||
| string const mechCsv = statsFilePath( "lagsolve" + p.suffix, "iterations" ); | ||
| string_view const headerToken = "Number of time steps"; | ||
|
|
||
| // 1) Coupled solver CSV: exists, exactly ONE header, and data rows. | ||
| CSVStats const coupled = inspectCSV( coupledCsv, headerToken ); | ||
| EXPECT_TRUE( coupled.exists ); | ||
| EXPECT_EQ( coupled.headerCount, 1 ); | ||
| EXPECT_GE( coupled.dataRowCount, 2 ); // one row per (sub)step, 2 forced steps | ||
|
|
||
| // 2) Sub-solver CSVs: in Sequential mode the sub-solvers iterate on their own, | ||
| // so their iteration files must contain data. | ||
| if( p.couplingType == "Sequential" ) | ||
| { | ||
| for( string const & path : { flowCsv, mechCsv } ) | ||
| { | ||
| CSVStats const sub = inspectCSV( path, headerToken ); | ||
| EXPECT_TRUE( sub.exists ) << path; | ||
| EXPECT_EQ( sub.headerCount, 1 ) << path; | ||
| EXPECT_GE( sub.dataRowCount, 2 ) << path; | ||
| } | ||
| } | ||
| // In FullyImplicit mode sub-solvers take no independent time steps, | ||
| // so writeIterationStatsToTable() early-returns: no rows expected. | ||
| } | ||
|
|
||
| TEST( CoupledSolverStatisticsCSVTest, checkCSVOutput ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIUC, as this is the first unit-test that tests |
||
| { | ||
| stdVector< Params > const scenarios = { | ||
| { "FullyImplicit", "all", "FIM" }, | ||
| { "Sequential", "all", "Seq" }, | ||
| }; | ||
|
|
||
| for( Params const & p : scenarios ) | ||
| { | ||
| runScenario( p ); | ||
| } | ||
| } | ||
|
|
||
| int main( int argc, char * * argv ) | ||
| { | ||
| ::testing::InitGoogleTest( &argc, argv ); | ||
| g_commandLineOptions = *geos::basicSetup( argc, argv ); | ||
| int const result = RUN_ALL_TESTS(); | ||
| geos::basicCleanup(); | ||
| return result; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it go before the first
if? Maybe a scopedifcan help.