diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 778bb19d9..000000000 --- a/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -cube-fr.dot -cube.dot -disconnected-fr.dot -graphml_test_out.xml -kevin-bacon2.dat -random.dot -triangular-fr.dot -triangular-kk.dot -test/*.dot -example/routing-table.dat -example/figs/ospf-sptree.dot -CMakeFiles/ diff --git a/example/euclidean_graph_generator_example.cpp b/example/euclidean_graph_generator_example.cpp new file mode 100644 index 000000000..2a5d90619 --- /dev/null +++ b/example/euclidean_graph_generator_example.cpp @@ -0,0 +1,185 @@ +//======================================================================= +// Copyright 2026 +// Author: Matyas W Egyhazy +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +//======================================================================= + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Utility function to write graph to GraphML file with point positions +template < typename Graph > +void write_graph_to_graphml(Graph& g, const std::string& filename, + const std::vector< boost::simple_point< double > >& points) +{ + std::ofstream file(filename); + if (!file) + { + std::cerr << "Error opening file: " << filename << std::endl; + return; + } + + // Create property maps for x and y coordinates + std::map< typename boost::graph_traits< Graph >::vertex_descriptor, double > + x_map, y_map; + + typename boost::graph_traits< Graph >::vertex_iterator vi, vi_end; + for (boost::tie(vi, vi_end) = vertices(g); vi != vi_end; ++vi) + { + std::size_t idx = boost::get(boost::vertex_index, g, *vi); + x_map[*vi] = points[idx].x; + y_map[*vi] = points[idx].y; + } + + boost::associative_property_map< std::map< + typename boost::graph_traits< Graph >::vertex_descriptor, double > > + x_pmap(x_map), y_pmap(y_map); + + // Create dynamic properties for GraphML output + boost::dynamic_properties dp; + dp.property("x", x_pmap); + dp.property("y", y_pmap); + dp.property("weight", boost::get(boost::edge_weight, g)); + + // Write GraphML + boost::write_graphml(file, g, dp, true); +} + +void example_basic_random_graph() +{ + using Graph = boost::adjacency_matrix< boost::undirectedS, + boost::no_property, boost::property< boost::edge_weight_t, double > >; + using Point = boost::simple_point< double >; + + const std::size_t num_vertices = 25; + Graph g(num_vertices); + + // Generate a container of random points w/ default rng and distribution (uniform) + std::vector< Point > points; + points.reserve(num_vertices); + auto back_itr = std::back_inserter(points); + boost::generate_random_points(num_vertices, 500, back_itr); + + // Get the edge weight map (matches edges to weight) and vertex index map + auto weight_map = boost::get(boost::edge_weight, g); + auto vertex_index_map = boost::get(boost::vertex_index, g); + + // Create complete graph (generate edges) + // using the stored points and populate weight map + boost::connect_all_euclidean(g, points, weight_map, + vertex_index_map); +} + +void example_custom_distribution() +{ + using Graph = boost::adjacency_matrix< boost::undirectedS, + boost::no_property, boost::property< boost::edge_weight_t, double > >; + using Point = boost::simple_point< double >; + + const std::size_t num_vertices = 15; + Graph g(num_vertices); + + std::normal_distribution< double > normal_dist(50.0, 10.0); + std::vector< Point > points; + points.reserve(num_vertices); + boost::generate_random_points( + num_vertices, normal_dist, normal_dist, std::back_inserter(points)); + + // Create graph + boost::connect_all_euclidean(g, points, boost::get(boost::edge_weight, g), + boost::get(boost::vertex_index, g)); +} + +void example_mst_on_euclidean_graph() +{ + // Use adjacency_list to make graphML printing possible + using Graph = boost::adjacency_list< boost::vecS, boost::vecS, + boost::undirectedS, boost::no_property, + boost::property< boost::edge_weight_t, double > >; + using Point = boost::simple_point< double >; + using Edge = boost::graph_traits< Graph >::edge_descriptor; + + const std::size_t num_vertices = 20; + Graph g(num_vertices); + + // Generate and store points + std::vector< Point > points; + points.reserve(num_vertices); + boost::generate_random_points( + num_vertices, 500, std::back_inserter(points)); + + // Create complete graph using adjacency_list + boost::connect_all_euclidean(g, points, boost::get(boost::edge_weight, g), + boost::get(boost::vertex_index, g)); + + // Write full graph with GraphML format + write_graph_to_graphml(g, "full_graph.graphml", points); + + // Compute MST + std::vector< Edge > mst_edges; + boost::kruskal_minimum_spanning_tree(g, std::back_inserter(mst_edges)); + + // Create MST subgraph using adjacency_list + Graph mst_graph(num_vertices); + for (const auto& e : mst_edges) + { + auto src = boost::source(e, g); + auto tgt = boost::target(e, g); + auto weight = boost::get(boost::edge_weight, g, e); + boost::add_edge(src, tgt, + boost::property< boost::edge_weight_t, double >(weight), mst_graph); + } + + // Write MST with GraphML format + write_graph_to_graphml(mst_graph, "mst_graph.graphml", points); +} + +void example_make_convenient_euclidean_graph() +{ + using Graph = boost::adjacency_matrix< boost::undirectedS, + boost::no_property, boost::property< boost::edge_weight_t, double > >; + + const std::size_t num_vertices = 10; + const std::size_t coord_max = 100; + Graph g(num_vertices); + + // Use simple convenience function to create complete graph + boost::make_random_euclidean_graph(g, num_vertices, coord_max, + boost::get(boost::edge_weight, g), boost::get(boost::vertex_index, g)); +} + +int main() +{ + // Generates a basic random Euclidean graph from convenience function + example_make_convenient_euclidean_graph(); + + // Generates a basic random Euclidean graph from building blocks + example_basic_random_graph(); + + // Shows how to use custom distributions (e.g. normal) with separate + // point generation and graph construction steps + example_custom_distribution(); + + // Computes MST on a + // generated Euclidean graph and output + // GraphML files which can be viewed with tools like + // Gephi or Cytoscape https://lite.gephi.org/ + example_mst_on_euclidean_graph(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/include/boost/graph/euclidean_graph_generator.hpp b/include/boost/graph/euclidean_graph_generator.hpp new file mode 100644 index 000000000..782174aa3 --- /dev/null +++ b/include/boost/graph/euclidean_graph_generator.hpp @@ -0,0 +1,255 @@ +//======================================================================= +// Copyright 2026 +// Author: Matyas W Egyhazy +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +//======================================================================= + +#ifndef BOOST_GRAPH_EUCLIDEAN_GRAPH_GENERATOR_HPP +#define BOOST_GRAPH_EUCLIDEAN_GRAPH_GENERATOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost +{ + +// connect_all_euclidean +// +// Creates a complete graph with Euclidean distance edge weights. +// Connects all vertices in the graph with edges weighted by the Euclidean +// distance between their corresponding points in the point container. +// This is a common preprocessing step for TSP algorithms. +// +// Preconditions: g must have num_vertices(g) == points.size() and no edges +// Postconditions: g will be a complete graph with Euclidean distance weights +// Complexity: O(V^2) where V is the number of vertices + +template < typename VertexListGraph, typename PointContainer, + typename WeightMap, typename VertexIndexMap > +void connect_all_euclidean(VertexListGraph& g, const PointContainer& points, + WeightMap wmap, VertexIndexMap vmap) +{ + BOOST_CONCEPT_ASSERT((VertexListGraphConcept< VertexListGraph >)); + BOOST_CONCEPT_ASSERT((WritablePropertyMapConcept< WeightMap, + typename graph_traits< VertexListGraph >::edge_descriptor >)); + BOOST_CONCEPT_ASSERT((ReadablePropertyMapConcept< VertexIndexMap, + typename graph_traits< VertexListGraph >::vertex_descriptor >)); + + // Precondition: Graph should have no edges + BOOST_ASSERT_MSG(boost::num_edges(g) == 0, + "connect_all_euclidean requires an empty graph (no edges)"); + + using Edge = typename graph_traits< VertexListGraph >::edge_descriptor; + using VItr = typename graph_traits< VertexListGraph >::vertex_iterator; + + // Deduce the weight type from the WeightMap's value type + using WeightType = typename boost::property_traits< WeightMap >::value_type; + + // Deduce coordinate type from the point container + using PointType = typename PointContainer::value_type; + using CoordType = decltype(std::declval().x); + using IndexType = typename boost::property_traits::value_type; + + + // Compile-time assertion: Prevent integer weight types + BOOST_STATIC_ASSERT_MSG( + !std::is_integral::value, + "connect_all_euclidean requires floating-point weight types (float, double, or long double). " + "Integer types cause truncation and produce non-useful incorrect tour lengths. " + "e.g. Use property instead of property." + ); + + + std::pair< VItr, VItr > verts(vertices(g)); + + for (VItr src(verts.first); src != verts.second; ++src) + { + const IndexType src_idx = boost::get(vmap, *src); // Cache source index lookup + + VItr dest(src); + ++dest; // Skip self-edge + + for (; dest != verts.second; ++dest) + { + const IndexType dest_idx = boost::get(vmap, *dest); // Cache destination index lookup + + // Promote to WeightType for calculation precision and to avoid overflow + const WeightType dx = + static_cast(points[src_idx].x) - + static_cast(points[dest_idx].x); + const WeightType dy = + static_cast(points[src_idx].y) - + static_cast(points[dest_idx].y); + + // Use std::hypot for numerically stable Euclidean distance + const WeightType weight = static_cast(std::hypot(dx, dy)); + + // No need to check 'inserted' - building fresh complete graph + Edge e = boost::add_edge(*src, *dest, g).first; + boost::put(wmap, e, weight); + } + } +} + +// generate_random_points +// +// Generates a set of random unique 2D points for TSP testing. +// Uses std::unordered_set to ensure uniqueness and avoid duplicate points. +// This involves copying points into the output iterator... +// but simplifies uniqueness handling. +// Supports custom random distributions for flexible point generation patterns. +// +// Returns: The number of unique points generated. +// +// Parameters: +// num_points - Number of unique points to generate +// x_dist - Distribution for x-coordinates (e.g., uniform, normal) +// y_dist - Distribution for y-coordinates (can differ from x) +// out - Output iterator for storing generated points +// rng - Random number generator (default: std::mt19937 with random seed) +// +// Postconditions: Exactly num_points unique points written to out +// Complexity: O(N) average case, O(N^2) worst case due to collision handling +template < typename OutputIterator, typename XDistribution, + typename YDistribution, typename RandomEngine > +std::size_t generate_random_points( + std::size_t num_points, + XDistribution x_dist, + YDistribution y_dist, + OutputIterator out, + RandomEngine& rng, + std::size_t max_attempts = 0) +{ + using CoordType = typename XDistribution::result_type; + BOOST_STATIC_ASSERT_MSG( + (std::is_same< CoordType, typename YDistribution::result_type >::value), + "X and Y distributions must have the same result type"); + + // This avoids the rare case in which the while loop runs indefinitely due to collisions + // when num_points is large and the distribution is narrow. + if (max_attempts == 0) + max_attempts = std::max(10 * num_points, 100); + + auto point_hash = [](const simple_point< CoordType >& p) + { + std::size_t seed = 0; + boost::hash_combine(seed, p.x); + boost::hash_combine(seed, p.y); + return seed; + }; + + auto point_equal = [](const simple_point< CoordType >& lhs, + const simple_point< CoordType >& rhs) + { return lhs.x == rhs.x && lhs.y == rhs.y; }; + + using Point = boost::simple_point< CoordType >; + using PointSet = boost::unordered_flat_set< Point, decltype(point_hash), + decltype(point_equal) >; + + PointSet point_set(0, point_hash, point_equal); + point_set.reserve(num_points); + + std::size_t attempts = 0; + while (point_set.size() < num_points && attempts < max_attempts) + { + point_set.insert(Point { x_dist(rng), y_dist(rng) }); + ++attempts; + } + + std::copy(std::make_move_iterator(point_set.begin()), + std::make_move_iterator(point_set.end()), out); + + return point_set.size(); +} + +// Overload with default RNG and max_attempts parameter +template < typename OutputIterator, typename XDistribution, + typename YDistribution > +std::size_t generate_random_points( + std::size_t num_points, + XDistribution x_dist, + YDistribution y_dist, + OutputIterator out, + std::size_t max_attempts = 0) +{ + std::mt19937 rng(std::random_device {}()); + return generate_random_points(num_points, x_dist, y_dist, out, rng, max_attempts); +} + +// Overload for uniform distribution, with max_attempts parameter +template < typename OutputIterator, typename CoordType = double > +std::size_t generate_random_points( + std::size_t num_points, + std::size_t coordinate_max, + OutputIterator out, + std::size_t max_attempts = 0) +{ + std::uniform_real_distribution< CoordType > dist( + static_cast< CoordType >(0), static_cast< CoordType >(coordinate_max)); + return generate_random_points(num_points, dist, dist, out, max_attempts); +} + +// make_random_euclidean_graph +// +// Creates a complete graph with random points and Euclidean distance weights. +// This is a convenience function that combines random point generation with +// complete graph construction for TSP testing and benchmarking. +// +// Parameters: +// g - Graph to populate (must have num_vertices(g) == num_points) +// num_points - Number of vertices/points +// coordinate_max - Maximum coordinate value for random points +// weight_map - Property map for storing edge weights +// vertex_index_map - Property map for vertex indices +// +// Postconditions: g is a complete graph with Euclidean distance weights +// Complexity: O(V^2) where V is the number of vertices +template < typename VertexListGraph, typename WeightMap, + typename VertexIndexMap, typename CoordType = double > +void make_random_euclidean_graph(VertexListGraph& g, std::size_t num_points, + std::size_t coordinate_max, WeightMap weight_map, + VertexIndexMap vertex_index_map) +{ + std::vector< simple_point< CoordType > > points; + points.reserve(num_points); + generate_random_points< decltype(std::back_inserter(points)), CoordType >( + num_points, coordinate_max, std::back_inserter(points)); + connect_all_euclidean(g, points, weight_map, vertex_index_map); +} + +// make_random_euclidean_graph (parameterized distribution version) +// +// Version with custom distribution support for flexible point generation. +template < typename VertexListGraph, typename WeightMap, + typename VertexIndexMap, typename XDistribution, typename YDistribution > +void make_random_euclidean_graph(VertexListGraph& g, std::size_t num_points, + XDistribution x_dist, YDistribution y_dist, WeightMap weight_map, + VertexIndexMap vertex_index_map) +{ + using CoordType = typename XDistribution::result_type; + std::vector< simple_point< CoordType > > points; + points.reserve(num_points); + generate_random_points( + num_points, x_dist, y_dist, std::back_inserter(points)); + connect_all_euclidean(g, points, weight_map, vertex_index_map); +} + +} // namespace boost + +#endif // BOOST_GRAPH_EUCLIDEAN_GRAPH_GENERATOR_HPP diff --git a/test/euclidean_graph_generator_test.cpp b/test/euclidean_graph_generator_test.cpp new file mode 100644 index 000000000..08cdd5c8f --- /dev/null +++ b/test/euclidean_graph_generator_test.cpp @@ -0,0 +1,335 @@ +//======================================================================= +// Copyright 2026 +// Author: Matyas W Egyhazy +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +//======================================================================= + +#define BOOST_TEST_MODULE euclidean_graph_generator_test +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace +{ + +// Type aliases for common graph types used in tests +using UndirectedListGraph + = boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS, + boost::no_property, boost::property< boost::edge_weight_t, double > >; + +using UndirectedMatrixGraph = boost::adjacency_matrix< boost::undirectedS, + boost::no_property, boost::property< boost::edge_weight_t, double > >; + +using PointDbl = boost::simple_point< double >; + +// Helper: Calculate Euclidean distance between two points +template < typename CoordType > +double euclidean_distance(const boost::simple_point< CoordType >& p1, + const boost::simple_point< CoordType >& p2) +{ + return std::hypot(p1.x - p2.x, p1.y - p2.y); +} + +// Helper: Verify that a graph is complete +template < typename Graph > +bool is_complete_graph(const Graph& g) +{ + const std::size_t n = boost::num_vertices(g); + const std::size_t expected_edges = (n * (n - 1)) / 2; + return boost::num_edges(g) == expected_edges; +} + +//======================================================================= +// Test Fixtures +//======================================================================= + +// Fixture for tests using random points with matrix graph +template < typename Graph > +struct RandomGraphFixture +{ + static constexpr std::size_t num_vertices = 10; + Graph g; + std::vector< PointDbl > points; + + RandomGraphFixture() : g(num_vertices) + { + boost::generate_random_points( + num_vertices, 100, std::back_inserter(points)); + boost::connect_all_euclidean(g, points, + boost::get(boost::edge_weight, g), + boost::get(boost::vertex_index, g)); + } +}; + +using MatrixGraphFixture = RandomGraphFixture< UndirectedMatrixGraph >; +using ListGraphFixture = RandomGraphFixture< UndirectedListGraph >; + +// Fixture for tests using known 3-4-5 triangle points +template < typename Graph, typename PointType > +struct KnownPointsFixture +{ + Graph g; + std::vector< PointType > points; + decltype(boost::get(boost::edge_weight, g)) weight_map; + + KnownPointsFixture() : g(5), weight_map(boost::get(boost::edge_weight, g)) + { + points = { { 0.0, 0.0 }, { 3.0, 0.0 }, { 0.0, 4.0 }, { 3.0, 4.0 }, + { 1.5, 2.0 } }; + boost::connect_all_euclidean( + g, points, weight_map, boost::get(boost::vertex_index, g)); + } +}; + +using MatrixKnownPointsFixture + = KnownPointsFixture< UndirectedMatrixGraph, PointDbl >; + +} // anonymous namespace + +//======================================================================= +// Test Suite: generate_random_points +//======================================================================= + +BOOST_AUTO_TEST_SUITE(generate_random_points_tests) + +BOOST_AUTO_TEST_CASE(test_uniqueness) +{ + const std::size_t num_points = 100; + std::vector< PointDbl > points; + + boost::generate_random_points( + num_points, 10000, std::back_inserter(points)); + + std::set< std::pair< double, double > > unique_points; + for (const auto& p : points) + { + unique_points.insert(std::make_pair(p.x, p.y)); + } + + BOOST_TEST(unique_points.size() == num_points); +} + +BOOST_AUTO_TEST_CASE(test_custom_distributions) +{ + const std::size_t num_points = 30; + std::vector< PointDbl > points; + + std::uniform_real_distribution< double > x_dist(0.0, 100.0); + std::uniform_real_distribution< double > y_dist(0.0, 200.0); + + std::size_t generated = boost::generate_random_points( + num_points, x_dist, y_dist, std::back_inserter(points)); + + BOOST_TEST(generated == num_points); + BOOST_TEST(points.size() == num_points); + + for (const auto& p : points) + { + BOOST_TEST(p.x >= 0.0); + BOOST_TEST(p.x <= 100.0); + BOOST_TEST(p.y >= 0.0); + BOOST_TEST(p.y <= 200.0); + } +} + +BOOST_AUTO_TEST_CASE(test_max_attempts) +{ + const std::size_t num_points = 100; + std::uniform_int_distribution< int > narrow_dist(0, 9); + std::mt19937 rng(12345); + std::vector< boost::simple_point< int > > points; + + std::size_t generated = boost::generate_random_points(num_points, + narrow_dist, narrow_dist, std::back_inserter(points), rng, 200); + + BOOST_TEST(generated < num_points); + BOOST_TEST(points.size() == generated); +} + +BOOST_AUTO_TEST_CASE(test_empty) +{ + std::vector< PointDbl > points; + std::size_t generated + = boost::generate_random_points(0, 100, std::back_inserter(points)); + + BOOST_TEST(generated == 0u); + BOOST_TEST(points.empty()); +} + +BOOST_AUTO_TEST_SUITE_END() + +//======================================================================= +// Test Suite: connect_all_euclidean (using fixtures) +//======================================================================= + +BOOST_AUTO_TEST_SUITE(connect_all_euclidean_tests) + +BOOST_FIXTURE_TEST_CASE(test_adjacency_matrix, MatrixGraphFixture) +{ + BOOST_TEST(is_complete_graph(g)); +} + +BOOST_FIXTURE_TEST_CASE(test_adjacency_list, ListGraphFixture) +{ + BOOST_TEST(is_complete_graph(g)); +} + +BOOST_FIXTURE_TEST_CASE(test_edge_weights_matrix, MatrixKnownPointsFixture) +{ + auto e01 = boost::edge(0, 1, g); + BOOST_REQUIRE(e01.second); + BOOST_TEST(boost::get(weight_map, e01.first) + == euclidean_distance(points[0], points[1]), + boost::test_tools::tolerance(1e-10)); + + auto e02 = boost::edge(0, 2, g); + BOOST_REQUIRE(e02.second); + BOOST_TEST(boost::get(weight_map, e02.first) + == euclidean_distance(points[0], points[2]), + boost::test_tools::tolerance(1e-10)); + + auto e03 = boost::edge(0, 3, g); + BOOST_REQUIRE(e03.second); + BOOST_TEST(boost::get(weight_map, e03.first) + == euclidean_distance(points[0], points[3]), + boost::test_tools::tolerance(1e-10)); +} + +BOOST_AUTO_TEST_CASE(test_single_vertex_matrix) +{ + UndirectedMatrixGraph g(1); + std::vector< PointDbl > points = { { 0.0, 0.0 } }; + + boost::connect_all_euclidean(g, points, boost::get(boost::edge_weight, g), + boost::get(boost::vertex_index, g)); + + BOOST_TEST(boost::num_edges(g) == 0u); +} + +BOOST_AUTO_TEST_SUITE_END() + +//======================================================================= +// Test Suite: make_random_euclidean_graph +//======================================================================= + +BOOST_AUTO_TEST_SUITE(make_random_euclidean_graph_tests) + +BOOST_AUTO_TEST_CASE(test_with_distributions_matrix) +{ + const std::size_t num_vertices = 20; + UndirectedMatrixGraph g(num_vertices); + + std::uniform_real_distribution< double > x_dist(0.0, 500.0); + std::normal_distribution< double > y_dist(250.0, 50.0); + + boost::make_random_euclidean_graph(g, num_vertices, x_dist, y_dist, + boost::get(boost::edge_weight, g), boost::get(boost::vertex_index, g)); + + BOOST_TEST(is_complete_graph(g)); +} + +BOOST_AUTO_TEST_SUITE_END() + +//======================================================================= +// Test Suite: float_precision +//======================================================================= + +BOOST_AUTO_TEST_SUITE(float_precision_tests) + +BOOST_AUTO_TEST_CASE(test_float_generation_and_math) +{ + const std::size_t num_points = 20; + std::vector< boost::simple_point< float > > points; + std::uniform_real_distribution< float > dist(0.0f, 100.0f); + + std::size_t generated = boost::generate_random_points( + num_points, dist, dist, std::back_inserter(points)); + + BOOST_TEST(generated == num_points); + + using FloatGraph = boost::adjacency_matrix< boost::undirectedS, + boost::no_property, boost::property< boost::edge_weight_t, float > >; + + FloatGraph g(3); + std::vector< boost::simple_point< float > > known_points + = { { 0.0f, 0.0f }, { 3.0f, 0.0f }, { 0.0f, 4.0f } }; + + boost::connect_all_euclidean(g, known_points, + boost::get(boost::edge_weight, g), boost::get(boost::vertex_index, g)); + + auto weight_map = boost::get(boost::edge_weight, g); + + auto e01 = boost::edge(0, 1, g); + BOOST_REQUIRE(e01.second); + BOOST_TEST(boost::get(weight_map, e01.first) + == euclidean_distance(known_points[0], known_points[1]), + boost::test_tools::tolerance(1e-5f)); + + auto e02 = boost::edge(0, 2, g); + BOOST_REQUIRE(e02.second); + BOOST_TEST(boost::get(weight_map, e02.first) + == euclidean_distance(known_points[0], known_points[2]), + boost::test_tools::tolerance(1e-5f)); + + auto e12 = boost::edge(1, 2, g); + BOOST_REQUIRE(e12.second); + BOOST_TEST(boost::get(weight_map, e12.first) + == euclidean_distance(known_points[1], known_points[2]), + boost::test_tools::tolerance(1e-5f)); +} + +BOOST_AUTO_TEST_SUITE_END() + +//======================================================================= +// Test Suite: triangle_inequality +//======================================================================= + +BOOST_AUTO_TEST_SUITE(triangle_inequality_tests) + +BOOST_FIXTURE_TEST_CASE( + test_euclidean_weights_satisfy_triangle_inequality_matrix, + MatrixGraphFixture) +{ + auto weight_map = boost::get(boost::edge_weight, g); + + for (std::size_t i = 0; i < num_vertices; ++i) + { + for (std::size_t j = 0; j < num_vertices; ++j) + { + if (i == j) + continue; + for (std::size_t k = 0; k < num_vertices; ++k) + { + if (k == i || k == j) + continue; + + auto e_ik = boost::edge(i, k, g); + auto e_ij = boost::edge(i, j, g); + auto e_jk = boost::edge(j, k, g); + + double d_ik = boost::get(weight_map, e_ik.first); + double d_ij = boost::get(weight_map, e_ij.first); + double d_jk = boost::get(weight_map, e_jk.first); + + BOOST_TEST(d_ik <= d_ij + d_jk + 1e-10); + } + } + } +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file