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+ #include < geode/geometry/point.hpp>
24+ #include < geode/stochastic/sampling/mcmc/metropolis_hasting_sampler.hpp>
25+ #include < geode/stochastic/sampling/mcmc/models/components/intensity_term.hpp>
26+ #include < geode/stochastic/sampling/mcmc/models/components/pairwise_term.hpp>
27+ #include < geode/stochastic/sampling/mcmc/models/gibbs_energy.hpp>
28+ #include < geode/stochastic/sampling/mcmc/proposal/classical_proposals.hpp>
29+ #include < geode/stochastic/sampling/mcmc/proposal/marked_object_sampler/uniform_marked_point_sampler.hpp>
30+ namespace
31+ {
32+
33+ void test_convergence ( double domain_length,
34+ double poisson_density,
35+ double gamma,
36+ double nb_points,
37+ double nb_paires )
38+ {
39+ geode::RandomEngine engine;
40+ engine.set_seed ( " @mh-test@" );
41+
42+ geode::Point2D min_point{ { 0 ., 0 . } };
43+ geode::Point2D max_point{ { domain_length, domain_length } };
44+
45+ geode::BoundingBox2D box;
46+ box.add_point ( min_point );
47+ box.add_point ( max_point );
48+
49+ double area = domain_length * domain_length;
50+
51+ geode::UniformMarkedPointSampler< 2 > sampler ( box, std::nullopt );
52+
53+ geode::GibbsEnergy< geode::Point2D > energy;
54+ energy.add_energy_term (
55+ std::make_unique< geode::IntensityTerm< geode::Point2D > >(
56+ poisson_density ) );
57+
58+ auto interaction_fn =
59+ []( const geode::MarkedObject< geode::Point2D >& a,
60+ const geode::MarkedObject< geode::Point2D >& b ) {
61+ auto dx = a.geometry ().value ( 0 ) - b.geometry ().value ( 0 );
62+ auto dy = a.geometry ().value ( 1 ) - b.geometry ().value ( 1 );
63+ auto dist_sq = dx * dx + dy * dy;
64+ return dist_sq < 2 ;
65+ };
66+
67+ geode::PairwiseTerm< geode::Point2D, decltype ( interaction_fn ) > term (
68+ gamma, interaction_fn );
69+ energy.add_energy_term ( std::make_unique<
70+ geode::PairwiseTerm< geode::Point2D, decltype ( interaction_fn ) > >(
71+ gamma, interaction_fn ) );
72+
73+ auto kernel1 =
74+ geode::create_birth_death_change_kernel< geode::Point2D >(
75+ sampler, 0.33 , 0.33 );
76+ geode::MetropolisHastings< geode::Point2D > mh (
77+ energy, std::move ( kernel1 ) );
78+
79+ geode::Configuration< geode::Point2D > state;
80+
81+ constexpr geode::index_t burn_in{ 5000 };
82+ constexpr geode::index_t N{ 100000 };
83+
84+ // Burn-in
85+ for ( const auto i : geode::Range{ burn_in } )
86+ {
87+ geode_unused ( i );
88+ mh.step ( state, engine );
89+ }
90+
91+ // Sampling
92+ double sum_points = 0.0 ;
93+ double sum_paires = 0.0 ;
94+ for ( const auto i : geode::Range{ N } )
95+ {
96+ mh.step ( state, engine );
97+ auto stats = energy.ordered_energy_term_statistics ( state );
98+ sum_points += stats[0 ];
99+ sum_paires += stats[1 ];
100+ }
101+
102+ double mean_points = sum_points / N;
103+ double mean_paires = sum_paires / N;
104+
105+ geode::Logger::info ( " [MH test] mean points = " , mean_points,
106+ " and mean paires = " , mean_paires, " (expected " , nb_points, " ; " ,
107+ nb_paires, " ) " );
108+
109+ OPENGEODE_EXCEPTION ( std::abs ( mean_points - nb_points ) < 1 ,
110+ " [MH test] unexpected nb points." );
111+
112+ // ------------------------------------------------------------
113+ // Variance test: Poisson => Var(N) ≈ E[N]
114+ // ------------------------------------------------------------
115+ if ( nb_paires == 0 )
116+ {
117+ OPENGEODE_EXCEPTION (
118+ mean_paires == 0 , " [MH test] unexpected nb paires." );
119+ }
120+ else
121+ {
122+ OPENGEODE_EXCEPTION ( std::abs ( mean_paires - nb_paires ) < 3 ,
123+ " [MH test] unexpected nb paires." );
124+ }
125+ }
126+ } // namespace
127+
128+ int main ()
129+ {
130+ try
131+ {
132+ geode::StochasticLibrary::initialize ();
133+
134+ double domain_length{ 10 . };
135+ double poisson_density_lambda{ 0.5 };
136+ std::array< double , 3 > gamma{ 0 , 0.5 , 1 . };
137+
138+ test_convergence ( domain_length, poisson_density_lambda, 0 ., 15 , 0 );
139+ test_convergence ( domain_length, poisson_density_lambda, 1 ., 50 , 70 );
140+ test_convergence ( domain_length, poisson_density_lambda, 0.5 , 24 , 10 );
141+
142+ geode::Logger::info ( " MH STATISTICS AND CONVERGENCE TEST SUCCESS" );
143+ return 0 ;
144+ }
145+ catch ( ... )
146+ {
147+ return geode::geode_lippincott ();
148+ }
149+ }
0 commit comments