Skip to content

Commit 93cbc02

Browse files
feat(SimulationHelper): add simulation printer class
1 parent e87a811 commit 93cbc02

2 files changed

Lines changed: 231 additions & 24 deletions

File tree

include/geode/stochastic/sampling/mcmc/helpers/simulation_printer.hpp

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include <geode/stochastic/common.hpp>
2626
// #include <geode/stochastic/sampling/mcmc/metropolis_hasting_sampler.hpp>
2727
// #include <geode/stochastic/sampling/mcmc/models/energy_term_collection.hpp>
28+
#include <geode/stochastic/sampling/mcmc/helpers/simulation_monitor.hpp>
29+
#include <geode/stochastic/spatial/object_sets.hpp>
2830

2931
#include <absl/strings/str_join.h>
3032
#include <filesystem>
@@ -37,6 +39,9 @@ namespace geode
3739
bool print_statistics{ true };
3840
std::string statistics_filename{ "statistics.txt" };
3941

42+
bool print_statistics_summary{ true };
43+
std::string statistics_summary_filename{ "statistics_summary.txt" };
44+
4045
bool print_realisations{ true };
4146
std::string realisations_prefix{ "pattern_" };
4247
index_t realisations_print_frequency{ 100 };
@@ -47,40 +52,40 @@ namespace geode
4752
class SimulationPrinter
4853
{
4954
public:
50-
SimulationPrinter( absl::string_view stats_file )
51-
: stats_file_( stats_file )
55+
SimulationPrinter( const SimulationPrinterConfigurator& config )
56+
: config_( config )
5257
{
53-
write_header_if_new( stats_file_, "Simulation Statistics\n" );
5458
}
5559

56-
// Print header if file does not exist
57-
void write_header_if_new(
58-
absl::string_view filename, absl::string_view header )
59-
{
60-
namespace fs = std::filesystem;
61-
fs::path file_path{ std::string( filename ) };
62-
if( !fs::exists( file_path ) )
63-
{
64-
std::ofstream file = open_file_with_dirs( filename );
65-
file << header;
66-
}
67-
}
68-
69-
void print_statistics( const std::vector< double >& stats )
60+
// Print statistics to the configured statistics file
61+
void print_statistics(
62+
const std::vector< double >& stats, absl::string_view header )
7063
{
64+
if( !config_.print_statistics )
65+
return;
66+
const auto stats_file_path =
67+
stats_file_path_.value_or( create_statistics_file( header ) );
7168
std::ofstream file =
72-
open_file_with_dirs( stats_file_, std::ios::app );
69+
open_file_with_dirs( stats_file_path, std::ios::app );
7370
file << absl::StrJoin( stats, " ; " ) << "\n";
7471
}
7572

7673
template < typename ObjectType >
7774
void print_object_sets( const ObjectSets< ObjectType >& object_sets,
78-
absl::string_view filename )
75+
index_t realization_id )
7976
{
77+
if( !config_.print_realisations )
78+
return;
79+
80+
const auto filename =
81+
( std::filesystem::path( config_.output_folder )
82+
/ absl::StrCat(
83+
config_.realisations_prefix, realization_id, ".txt" ) )
84+
.string();
85+
8086
std::ofstream file = open_file_with_dirs( filename );
8187

8288
const auto all_objects = object_sets.get_all_object();
83-
8489
file << "#nb_objects\t" << all_objects.size() << "\n";
8590

8691
for( const auto& object_id : all_objects )
@@ -90,13 +95,44 @@ namespace geode
9095
<< "\n";
9196
}
9297
}
98+
void print_statistics_summary( const StatisticsMonitor& monitor,
99+
absl::string_view energy_term_names )
100+
{
101+
if( !config_.print_statistics_summary )
102+
return;
103+
104+
const auto summary_path =
105+
( std::filesystem::path( config_.output_folder )
106+
/ config_.statistics_summary_filename )
107+
.string();
108+
109+
std::ofstream file = open_file_with_dirs( summary_path );
110+
file << "# Summary statistics\n";
111+
file << energy_term_names.data() << "\n";
112+
file << absl::StrCat( "# Count:\n", monitor.count, "\n" );
113+
file << absl::StrCat(
114+
"# Means:\n", absl::StrJoin( monitor.means, " ; " ), "\n" );
115+
file << absl::StrCat( "# Variances:\n",
116+
absl::StrJoin( monitor.variances, " ; " ), "\n" );
117+
}
93118

94-
// Reusable helper to open files with directories created
95-
std::ofstream open_file_with_dirs( absl::string_view path_filename,
96-
std::ios::openmode mode = std::ofstream::out )
119+
private:
120+
void write_header_if_new(
121+
absl::string_view filename, absl::string_view header )
97122
{
98123
namespace fs = std::filesystem;
124+
fs::path file_path{ std::string( filename ) };
125+
if( !fs::exists( file_path ) )
126+
{
127+
std::ofstream file = open_file_with_dirs( filename );
128+
file << header;
129+
}
130+
}
99131

132+
std::ofstream open_file_with_dirs( absl::string_view path_filename,
133+
std::ios::openmode mode = std::ofstream::out ) const
134+
{
135+
namespace fs = std::filesystem;
100136
fs::path file_path{ std::string( path_filename ) };
101137

102138
if( !file_path.has_parent_path() )
@@ -112,9 +148,24 @@ namespace geode
112148

113149
return file;
114150
}
151+
const std::string& create_statistics_file( absl::string_view header )
152+
{
153+
stats_file_path_ = ( std::filesystem::path( config_.output_folder )
154+
/ config_.statistics_filename )
155+
.string();
156+
157+
if( config_.print_statistics )
158+
{
159+
write_header_if_new( *stats_file_path_,
160+
absl::StrCat(
161+
"# Simulation Statistics\n", header.data(), "\n" ) );
162+
}
163+
return *stats_file_path_;
164+
}
115165

116166
private:
117-
std::string stats_file_;
167+
SimulationPrinterConfigurator config_;
168+
std::optional< std::string > stats_file_path_;
118169
};
119170

120171
} // namespace geode
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright (c) 2019 - 2025 Geode-solutions
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
* SOFTWARE.
21+
*
22+
*/
23+
24+
#include <geode/stochastic/common.hpp>
25+
#include <geode/stochastic/sampling/mcmc/helpers/simulation_monitor.hpp>
26+
#include <geode/stochastic/sampling/mcmc/helpers/simulation_printer.hpp>
27+
28+
#include <filesystem>
29+
#include <fstream>
30+
31+
namespace
32+
{
33+
void test_print_statistics(
34+
const geode::SimulationPrinterConfigurator& config )
35+
{
36+
geode::Logger::info( "[TEST] SimulationPrinter print statistics" );
37+
38+
geode::SimulationPrinter printer( config );
39+
40+
// --- Test print_statistics
41+
std::vector< double > stats = { 1.0, 2.5, 3.7 };
42+
printer.print_statistics(
43+
stats, "EnergyTerm1;EnergyTerm2;EnergyTerm3" );
44+
45+
const std::filesystem::path temp_folder = config.output_folder;
46+
const auto stats_path = temp_folder / config.statistics_filename;
47+
OPENGEODE_EXCEPTION( std::filesystem::exists( stats_path ),
48+
"Statistics file not created" );
49+
50+
std::ifstream stats_file( stats_path );
51+
std::string line;
52+
std::getline( stats_file, line );
53+
OPENGEODE_EXCEPTION(
54+
line == "# Simulation Statistics", "Header not correctly written" );
55+
std::getline( stats_file, line );
56+
OPENGEODE_EXCEPTION( line == "EnergyTerm1;EnergyTerm2;EnergyTerm3",
57+
"Header not correctly written" );
58+
std::getline( stats_file, line );
59+
OPENGEODE_EXCEPTION(
60+
line == "1 ; 2.5 ; 3.7", "Statistics line not correctly written" );
61+
62+
geode::Logger::info( "--> Success!" );
63+
}
64+
65+
void test_statistics_summary(
66+
const geode::SimulationPrinterConfigurator& config )
67+
{
68+
geode::Logger::info(
69+
"[TEST] SimulationPrinter print statistics summary" );
70+
71+
geode::SimulationPrinter printer( config );
72+
geode::StatisticsMonitor monitor( 2 );
73+
monitor.add_realization( { 1, 2 } );
74+
monitor.finalize();
75+
76+
printer.print_statistics_summary( monitor, "EnergyTerm1;EnergyTerm2" );
77+
78+
const std::filesystem::path temp_folder = config.output_folder;
79+
const auto summary_path =
80+
temp_folder / config.statistics_summary_filename;
81+
82+
OPENGEODE_EXCEPTION( std::filesystem::exists( summary_path ),
83+
"Summary file not created" );
84+
85+
std::ifstream summary_file( summary_path );
86+
std::string content(
87+
( std::istreambuf_iterator< char >( summary_file ) ),
88+
std::istreambuf_iterator< char >() );
89+
OPENGEODE_EXCEPTION(
90+
content.find( "EnergyTerm1;EnergyTerm2" ) != std::string::npos,
91+
"Energy term names missing" );
92+
OPENGEODE_EXCEPTION(
93+
content.find( "2" ) != std::string::npos, "Count missing" );
94+
OPENGEODE_EXCEPTION(
95+
content.find( "1 ; 2" ) != std::string::npos, "Means missing" );
96+
OPENGEODE_EXCEPTION(
97+
content.find( "0 ; 0" ) != std::string::npos, "Variances missing" );
98+
99+
geode::Logger::info( "--> Success!" );
100+
}
101+
102+
void test_print_objects(
103+
const geode::SimulationPrinterConfigurator& config )
104+
{
105+
geode::Logger::info(
106+
"[TEST] SimulationPrinter print statistics summary" );
107+
108+
geode::ObjectSets< geode::Point2D > object_sets;
109+
const auto set_id = object_sets.add_set( "default_name" );
110+
111+
object_sets.add_object( geode::Point2D{ { 0.0, 0.0 } }, set_id );
112+
object_sets.add_object( geode::Point2D{ { 1.0, 0.0 } }, set_id );
113+
object_sets.add_object( geode::Point2D{ { 3.0, 0.0 } }, set_id );
114+
115+
geode::SimulationPrinter printer( config );
116+
printer.print_object_sets( object_sets, 1 );
117+
118+
const std::filesystem::path temp_folder = config.output_folder;
119+
120+
const auto obj_path = temp_folder / "pattern_1.txt";
121+
OPENGEODE_EXCEPTION( std::filesystem::exists( obj_path ),
122+
"Object sets file not created" );
123+
124+
geode::Logger::info( "--> Success!" );
125+
}
126+
127+
} // namespace
128+
129+
int main()
130+
{
131+
try
132+
{
133+
geode::StochasticLibrary::initialize();
134+
geode::Logger::set_level( geode::Logger::LEVEL::debug );
135+
136+
const std::filesystem::path temp_folder =
137+
std::filesystem::temp_directory_path() / "simprinter_test";
138+
SDEBUG( temp_folder );
139+
std::filesystem::create_directories( temp_folder );
140+
141+
geode::SimulationPrinterConfigurator config;
142+
config.output_folder = temp_folder.string();
143+
config.statistics_filename = "stats.txt";
144+
config.statistics_summary_filename = "summary.txt";
145+
config.realisations_prefix = "pattern_";
146+
147+
test_print_statistics( config );
148+
test_statistics_summary( config );
149+
test_print_objects( config );
150+
return 0;
151+
}
152+
catch( ... )
153+
{
154+
return geode::geode_lippincott();
155+
}
156+
}

0 commit comments

Comments
 (0)