Skip to content

Commit 859da42

Browse files
feat(SimulationRunner): implement for poisson point process
1 parent 3f2dc47 commit 859da42

19 files changed

Lines changed: 446 additions & 234 deletions
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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+
#pragma once
25+
#include <geode/stochastic/sampling/mcmc/metropolis_hasting_sampler.hpp>
26+
#include <geode/stochastic/sampling/mcmc/models/energy_term_collection.hpp>
27+
28+
#include "absl/strings/str_join.h"
29+
#include <fstream>
30+
31+
namespace geode
32+
{
33+
class opengeode_stochastic_stochastic_api MonitoringStatistics
34+
{
35+
public:
36+
std::vector< double > sum;
37+
std::vector< double > sum_squares;
38+
std::vector< double > means;
39+
std::vector< double > variances;
40+
41+
MonitoringStatistics( const index_t nb_energy_terms )
42+
{
43+
sum.resize( nb_energy_terms, 0.0 );
44+
sum_squares.resize( nb_energy_terms, 0.0 );
45+
means.resize( nb_energy_terms, 0.0 );
46+
variances.resize( nb_energy_terms, 0.0 );
47+
}
48+
49+
void add_realization( const std::vector< double >& values )
50+
{
51+
for( const auto stat_id : Range{ values.size() } )
52+
{
53+
sum[stat_id] += values[stat_id];
54+
sum_squares[stat_id] += values[stat_id] * values[stat_id];
55+
}
56+
}
57+
58+
void finalize( const index_t nb_realizations )
59+
{
60+
for( const auto stat_id : Range{ sum.size() } )
61+
{
62+
means[stat_id] = sum[stat_id] / nb_realizations;
63+
double variance =
64+
( sum_squares[stat_id]
65+
- ( sum[stat_id] * sum[stat_id] ) / nb_realizations )
66+
/ ( nb_realizations - 1 );
67+
variances[stat_id] = variance;
68+
// stddevs[stat_id] =std::sqrt( std::max( variance, 0.0 ) );
69+
}
70+
}
71+
};
72+
73+
template < typename ObjectType >
74+
class SimulationRunner
75+
{
76+
public:
77+
SimulationRunner() = default;
78+
virtual ~SimulationRunner() = default;
79+
80+
virtual void initialize() = 0;
81+
82+
const ObjectSets< ObjectType >& run(
83+
RandomEngine& engine, const index_t steps )
84+
{
85+
mh_sampler_->walk( object_sets_, engine, steps );
86+
return object_sets_;
87+
}
88+
89+
void run_and_print( std::string_view filename,
90+
RandomEngine& engine,
91+
const index_t steps,
92+
const index_t nb_realizations )
93+
{
94+
const auto file_exist =
95+
static_cast< bool >( std::ifstream( filename.data() ) );
96+
if( !file_exist )
97+
{
98+
const auto header = statistics_header_file();
99+
print_to_file( filename, header );
100+
}
101+
102+
for( const auto realization : Range{ nb_realizations } )
103+
{
104+
run( engine, steps );
105+
const auto statistics = statistics_string();
106+
print_to_file( filename, statistics );
107+
}
108+
}
109+
110+
MonitoringStatistics run_print_and_monitor( std::string_view filename,
111+
RandomEngine& engine,
112+
const index_t steps,
113+
const index_t nb_realizations )
114+
{
115+
const auto file_exist =
116+
static_cast< bool >( std::ifstream( filename.data() ) );
117+
if( !file_exist )
118+
{
119+
const auto header = statistics_header_file();
120+
print_to_file( filename, header );
121+
}
122+
MonitoringStatistics stat_monitoring(
123+
energy_terms_collection_.size() );
124+
125+
for( const auto realization : Range{ nb_realizations } )
126+
{
127+
run( engine, steps );
128+
const auto stats = statistics();
129+
print_to_file( filename,
130+
absl::StrCat( absl::StrJoin( stats, " ; " ), "\n" ) );
131+
stat_monitoring.add_realization( stats );
132+
}
133+
stat_monitoring.finalize( nb_realizations );
134+
return stat_monitoring;
135+
}
136+
137+
const ObjectSets< ObjectType >& current_pattern_realization() const
138+
{
139+
return object_sets_;
140+
}
141+
142+
std::vector< double > statistics() const
143+
{
144+
std::vector< double > statistic_values;
145+
statistic_values.reserve( ordered_energy_terms_.size() );
146+
147+
for( const auto& energy_term_uuid : ordered_energy_terms_ )
148+
{
149+
const auto& term =
150+
energy_terms_collection_.get( energy_term_uuid );
151+
statistic_values.push_back( term.statistic( object_sets_ ) );
152+
}
153+
154+
return statistic_values;
155+
}
156+
157+
std::string statistics_log_info() const
158+
{
159+
std::string message( "Pattern statistics: " );
160+
for( const auto term_id :
161+
geode::Range{ ordered_energy_terms_.size() } )
162+
{
163+
const auto& energy_term = energy_terms_collection_.get(
164+
ordered_energy_terms_[term_id] );
165+
const double value = energy_term.statistic( object_sets_ );
166+
absl::StrAppend( &message, " \t Term(", energy_term.name(),
167+
") --> value/traget: ", value, " / ",
168+
ordered_target_statistics_[term_id] );
169+
}
170+
return message;
171+
}
172+
173+
protected:
174+
std::string energy_term_names() const
175+
{
176+
std::vector< std::string > term_names;
177+
term_names.reserve( ordered_energy_terms_.size() );
178+
179+
for( const auto& energy_term_uuid : ordered_energy_terms_ )
180+
{
181+
const auto& term =
182+
energy_terms_collection_.get( energy_term_uuid );
183+
term_names.push_back( term.name().data() );
184+
}
185+
186+
return absl::StrCat( absl::StrJoin( term_names, " ; " ), "\n" );
187+
}
188+
189+
std::string statistics_string() const
190+
{
191+
return absl::StrCat( absl::StrJoin( statistics(), " ; " ), "\n" );
192+
}
193+
194+
std::string statistics_header_file()
195+
{
196+
std::string message( "Sufficient statistics mcmc iterations:\n" );
197+
absl::StrAppend( &message, energy_term_names() );
198+
return message;
199+
}
200+
201+
void print_to_file(
202+
absl::string_view filename, absl::string_view message )
203+
{
204+
std::ofstream file(
205+
filename.data(), std::ofstream::out | std::ofstream::app );
206+
file << message;
207+
file.close();
208+
return;
209+
}
210+
211+
protected:
212+
std::vector< std::unique_ptr< geode::ObjectSetSampler< ObjectType > > >
213+
set_samplers_;
214+
215+
std::vector< geode::uuid > ordered_energy_terms_;
216+
std::vector< double > ordered_target_statistics_;
217+
218+
EnergyTermCollection< ObjectType > energy_terms_collection_;
219+
std::unique_ptr< geode::MetropolisHastings< ObjectType > > mh_sampler_;
220+
221+
ObjectSets< ObjectType > object_sets_;
222+
};
223+
} // namespace geode

include/geode/stochastic/sampling/mcmc/metropolis_hasting_sampler.hpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ namespace geode
4949
class MetropolisHastings
5050
{
5151
public:
52-
MetropolisHastings( GibbsEnergy< ObjectType >& energy,
52+
MetropolisHastings(
53+
const EnergyTermCollection< ObjectType >& energy_term_collection,
5354
std::unique_ptr< ProposalKernel< ObjectType > > proposal_kernel )
54-
: energy_( energy ),
55+
: gibbs_energy_{ energy_term_collection },
5556
proposal_kernel_( std::move( proposal_kernel ) )
5657
{
5758
OPENGEODE_ASSERT(
@@ -188,7 +189,7 @@ namespace geode
188189
{
189190
const auto new_object = proposal.new_object();
190191
const auto delta_log_energy =
191-
energy_.delta_log_add( state, new_object );
192+
gibbs_energy_.delta_log_add( state, new_object );
192193
return accept_or_reject( proposal, state, engine, delta_log_energy,
193194
[]( auto& state, auto& proposal ) {
194195
state.add_object(
@@ -203,7 +204,7 @@ namespace geode
203204
{
204205
const auto old_object_id = proposal.old_object_id();
205206
const auto delta_log_energy =
206-
energy_.delta_log_remove( state, old_object_id );
207+
gibbs_energy_.delta_log_remove( state, old_object_id );
207208
return accept_or_reject( proposal, state, engine, delta_log_energy,
208209
[]( auto& state, auto& proposal ) {
209210
state.remove_object( proposal.old_object_id() );
@@ -216,8 +217,8 @@ namespace geode
216217
{
217218
const auto new_object = proposal.new_object();
218219
const auto old_object_id = proposal.old_object_id();
219-
const auto delta_log_energy =
220-
energy_.delta_log_change( state, old_object_id, new_object );
220+
const auto delta_log_energy = gibbs_energy_.delta_log_change(
221+
state, old_object_id, new_object );
221222
// should we test that objects are in the same group?
222223
// should be ensured by the dynamic
223224
return accept_or_reject( proposal, state, engine, delta_log_energy,
@@ -229,7 +230,7 @@ namespace geode
229230
};
230231

231232
private:
232-
const GibbsEnergy< ObjectType >& energy_;
233+
GibbsEnergy< ObjectType > gibbs_energy_;
233234
std::unique_ptr< ProposalKernel< ObjectType > > proposal_kernel_;
234235
double beta_{ 1.0 };
235236
};

include/geode/stochastic/sampling/mcmc/models/components/energy_term_collection.hpp renamed to include/geode/stochastic/sampling/mcmc/models/energy_term_collection.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ namespace geode
6363
return energy_terms_.size();
6464
}
6565

66-
[[nodiscard]] std::shared_ptr< const EnergyTerm< ObjectType > > get(
66+
[[nodiscard]] const EnergyTerm< ObjectType >& get(
6767
const uuid& id ) const
6868
{
6969
auto it = energy_terms_.find( id );
7070
OPENGEODE_EXCEPTION( it != energy_terms_.end(),
7171
absl::StrCat( "[EnergyTermCollection] Unknown energy term: ",
7272
id.string() ) );
73-
return it->second;
73+
return *it->second;
7474
}
7575

7676
[[nodiscard]] const absl::flat_hash_map< uuid,

include/geode/stochastic/sampling/mcmc/models/gibbs_energy.hpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*/
2323
#pragma once
2424

25-
#include <geode/stochastic/sampling/mcmc/models/components/energy_term_collection.hpp>
25+
#include <geode/stochastic/sampling/mcmc/models/energy_term_collection.hpp>
2626
#include <geode/stochastic/spatial/object_sets.hpp>
2727

2828
namespace geode
@@ -65,8 +65,7 @@ namespace geode
6565
{
6666
double log_energy = 0.0;
6767
for( const auto& term :
68-
energy_terms_collection_.terms_for_set( new_object
69-
.set_id ) ) // energy_terms_collection_.all_terms() )
68+
energy_terms_collection_.terms_for_set( new_object.set_id ) )
7069
{
7170
log_energy += term->delta_log_add( state, new_object );
7271
}
@@ -77,8 +76,8 @@ namespace geode
7776
const ObjectSets< ObjectType >& state, const ObjectId& id ) const
7877
{
7978
double log_energy = 0.0;
80-
for( const auto& term : energy_terms_collection_.terms_for_set(
81-
id.set_id ) ) // energy_terms_collection_.all_terms() )
79+
for( const auto& term :
80+
energy_terms_collection_.terms_for_set( id.set_id ) )
8281
{
8382
log_energy += term->delta_log_remove( state, id );
8483
}
@@ -90,8 +89,8 @@ namespace geode
9089
const ObjectRef< ObjectType >& new_object ) const
9190
{
9291
double log_energy = 0.0;
93-
for( const auto& term : energy_terms_collection_.terms_for_set(
94-
old_id.set_id ) ) // energy_terms_collection_.all_terms() )
92+
for( const auto& term :
93+
energy_terms_collection_.terms_for_set( old_id.set_id ) )
9594
{
9695
log_energy +=
9796
term->delta_log_change( state, old_id, new_object );

include/geode/stochastic/sampling/mcmc/proposal/classical_proposals.hpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ namespace geode
3434
void add_birth_death_change_moves(
3535
std::unique_ptr< geode::ObjectSetSampler< ObjectType > >& sampler,
3636
geode::ProposalKernel< ObjectType >& kernel,
37+
const uuid& set_id,
3738
double birth_ratio,
3839
double death_ratio,
3940
double change_ratio )
@@ -44,14 +45,14 @@ namespace geode
4445

4546
const auto p_birth = birth_ratio / total_ratio;
4647
kernel.add_move(
47-
std::make_unique< geode::BirthDeathMove< ObjectType > >(
48-
sampler, total_ratio, p_birth ) );
48+
set_id, std::make_unique< geode::BirthDeathMove< ObjectType > >(
49+
*sampler, total_ratio, p_birth ) );
4950

5051
if( change_ratio > 0. )
5152
{
5253
kernel.add_move(
53-
std::make_unique< geode::ChangeMove< ObjectType > >(
54-
sampler, change_ratio ) );
54+
set_id, std::make_unique< geode::ChangeMove< ObjectType > >(
55+
*sampler, change_ratio ) );
5556
}
5657
}
5758

include/geode/stochastic/spatial/object_set.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace geode
3636
ObjectSet( ObjectSet&& ) noexcept = default;
3737
ObjectSet& operator=( ObjectSet&& ) noexcept = default;
3838

39+
void set_name( std::string_view name );
3940
const Type& get_object( index_t index ) const;
4041

4142
index_t add_object( Type&& object );

include/geode/stochastic/spatial/object_sets.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ namespace geode
6262
index_t nb_objects_in_set( const uuid& set_id ) const;
6363
index_t nb_objects() const;
6464

65-
uuid add_set();
65+
uuid add_set( std::string_view name );
6666
ObjectId add_object( Type&& object, const uuid& set_id );
6767
void update_object( const ObjectId& object_id, Type&& object );
6868
void remove_object( const ObjectId& object_id );

src/geode/stochastic/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ add_geode_library(
4848
"sampling/direct/double_sampler.hpp"
4949
"sampling/direct/point_uniform_sampler.hpp"
5050
"sampling/direct/segment_uniform_sampler.hpp"
51+
"sampling/mcmc/helpers/simulation_runner.hpp"
5152
"sampling/mcmc/models/components/pairwise_term.hpp"
5253
"sampling/mcmc/models/components/density_term.hpp"
53-
"sampling/mcmc/models/components/energy_term_collection.hpp"
5454
"sampling/mcmc/models/components/energy_term.hpp"
55+
"sampling/mcmc/models/energy_term_collection.hpp"
5556
"sampling/mcmc/models/gibbs_energy.hpp"
5657
"sampling/mcmc/proposal/classical_proposals.hpp"
5758
"sampling/mcmc/proposal/moves.hpp"

0 commit comments

Comments
 (0)