|
| 1 | +# Pseudocode: pgr_makeBiconnectedPlanar C++ Wrapper |
| 2 | + |
| 3 | +**GSoC 2026 — Community Bonding Deliverable** |
| 4 | +**Author:** Mohit Rawat (@Mohit242-bit) |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Overview |
| 9 | + |
| 10 | +This document describes the pseudocode for the C++ wrapper class `Pgr_makeBiconnectedPlanar` |
| 11 | +that will call `boost::make_biconnected_planar` and return the set of edges needed to make |
| 12 | +a connected planar graph biconnected while preserving planarity. |
| 13 | + |
| 14 | +**Return type:** `II_t_rt` (two BIGINT fields: `start_vid`, `end_vid`) |
| 15 | +The `seq` column is generated by the C SRF wrapper as `call_cntr + 1`, not stored in the struct — |
| 16 | +same pattern as `pgr_makeConnected`. |
| 17 | + |
| 18 | +**Files modeled after:** |
| 19 | +- `include/components/makeConnected.hpp` — for the wrapper class structure and mutable graph pattern |
| 20 | +- `include/planar/boyerMyrvold.hpp` — for planarity testing and the planar module pattern |
| 21 | +- `src/planar/boyerMyrvold_driver.cpp` — for the driver error handling pattern |
| 22 | +- `src/components/makeConnected_driver.cpp` — for the `II_t_rt` return and SRF pattern |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## Wrapper Class: `Pgr_makeBiconnectedPlanar<G>` |
| 27 | + |
| 28 | +``` |
| 29 | +FILE: include/planar/makeBiconnectedPlanar.hpp |
| 30 | +
|
| 31 | +CLASS Pgr_makeBiconnectedPlanar<G> : public Pgr_messages |
| 32 | +
|
| 33 | + TYPES (inherited from graph G): |
| 34 | + V = vertex_descriptor |
| 35 | + E = edge_descriptor |
| 36 | + E_i = edge_iterator |
| 37 | +
|
| 38 | + PUBLIC: |
| 39 | + FUNCTION makeBiconnectedPlanar(graph) -> vector<II_t_rt>: |
| 40 | + RETURN generateMakeBiconnectedPlanar(graph) |
| 41 | +
|
| 42 | + PRIVATE: |
| 43 | + FUNCTION generateMakeBiconnectedPlanar(graph) -> vector<II_t_rt>: |
| 44 | +
|
| 45 | + // ────────────────────────────────────────────── |
| 46 | + // Step 1: Compute planar embedding via Boyer-Myrvold |
| 47 | + // ────────────────────────────────────────────── |
| 48 | + // NOTE: The existing boyerMyrvold.hpp only does a boolean planarity check. |
| 49 | + // We need the embedding as a side-effect, so we call |
| 50 | + // boyer_myrvold_planarity_test with the embedding parameter directly. |
| 51 | + // This is NEW code — not reusing boyerMyrvold.hpp. |
| 52 | +
|
| 53 | + embedding = vector<vector<edge_descriptor>>(num_vertices(graph.graph)) |
| 54 | +
|
| 55 | + CHECK_FOR_INTERRUPTS() |
| 56 | +
|
| 57 | + is_planar = boyer_myrvold_planarity_test( |
| 58 | + boyer_myrvold_params::graph = graph.graph, |
| 59 | + boyer_myrvold_params::embedding = &embedding[0] |
| 60 | + ) |
| 61 | +
|
| 62 | + IF NOT is_planar: |
| 63 | + throw std::string("Graph is not planar.") |
| 64 | +
|
| 65 | + // ────────────────────────────────────────────── |
| 66 | + // Step 2: Build EdgeIndexMap |
| 67 | + // ────────────────────────────────────────────── |
| 68 | + // pgRouting's UndirectedGraph is: |
| 69 | + // adjacency_list<vecS, vecS, undirectedS, Basic_vertex, Basic_edge> |
| 70 | + // |
| 71 | + // Basic_edge has: source, target, id, cost |
| 72 | + // It does NOT have property<edge_index_t, int> as an interior property. |
| 73 | + // |
| 74 | + // Therefore boost::get(edge_index, g) will NOT work. |
| 75 | + // We must construct the edge index map explicitly using std::map. |
| 76 | + // |
| 77 | + // The standalone tests use property<edge_index_t, int> in the graph |
| 78 | + // definition, which is why get(edge_index, g) works there. |
| 79 | + // pgRouting's graph does not have this — confirmed from base_graph.hpp. |
| 80 | +
|
| 81 | + edge_index_map = std::map<edge_descriptor, int> |
| 82 | + edge_count = 0 |
| 83 | + FOR (ei, ei_end) = edges(graph.graph); ei != ei_end; ++ei: |
| 84 | + edge_index_map[*ei] = edge_count++ |
| 85 | +
|
| 86 | + edge_index_pmap = boost::associative_property_map<EdgeIndexMap>(edge_index_map) |
| 87 | +
|
| 88 | + // ────────────────────────────────────────────── |
| 89 | + // Step 3: Custom visitor (NON-MUTATING) |
| 90 | + // ────────────────────────────────────────────── |
| 91 | + // For makeBiconnectedPlanar, the visitor does NOT call add_edge(). |
| 92 | + // The algorithm identifies all needed edges in a single pass without |
| 93 | + // re-reading adjacency, so non-mutating is correct here. |
| 94 | + // |
| 95 | + // This is DIFFERENT from makeMaximalPlanar which MUST be mutating. |
| 96 | +
|
| 97 | + STRUCT pgr_collect_edges_visitor: |
| 98 | + result: vector<pair<V, V>>& |
| 99 | +
|
| 100 | + FUNCTION visit_vertex_pair(u, v, g): |
| 101 | + result.push_back({u, v}) |
| 102 | + // NOTE: No add_edge() call — intentionally non-mutating |
| 103 | +
|
| 104 | + collected_edges = vector<pair<V, V>> |
| 105 | + visitor = pgr_collect_edges_visitor{collected_edges} |
| 106 | +
|
| 107 | + // ────────────────────────────────────────────── |
| 108 | + // Step 4: Call Boost algorithm |
| 109 | + // ────────────────────────────────────────────── |
| 110 | + // Using Overload 1 (4 parameters) because we have an explicit EdgeIndexMap. |
| 111 | + // Cannot use Overload 3 (2 parameters, calls get(edge_index, g)) since |
| 112 | + // pgRouting's graph has no interior edge_index_t property. |
| 113 | +
|
| 114 | + TRY: |
| 115 | + boost::make_biconnected_planar( |
| 116 | + graph.graph, |
| 117 | + &embedding[0], |
| 118 | + edge_index_pmap, |
| 119 | + visitor |
| 120 | + ) |
| 121 | + CATCH boost::exception -> rethrow |
| 122 | + CATCH std::exception -> rethrow |
| 123 | + CATCH ... -> rethrow |
| 124 | +
|
| 125 | + // ────────────────────────────────────────────── |
| 126 | + // Step 5: Map Boost descriptors → pgRouting IDs |
| 127 | + // ────────────────────────────────────────────── |
| 128 | + // graph[descriptor].id gives the original pgRouting vertex ID. |
| 129 | + // This is provided by Pgr_base_graph — "spoonfed" by the framework. |
| 130 | + // No manual ID map construction needed. |
| 131 | +
|
| 132 | + results = vector<II_t_rt>(collected_edges.size()) |
| 133 | + FOR i = 0; i < collected_edges.size(); i++: |
| 134 | + src_id = graph[collected_edges[i].first].id // pgRouting vertex ID |
| 135 | + tgt_id = graph[collected_edges[i].second].id // pgRouting vertex ID |
| 136 | + results[i] = {src_id, tgt_id} |
| 137 | +
|
| 138 | + log << "Edges to add for biconnectivity: " << results.size() |
| 139 | +
|
| 140 | + RETURN results |
| 141 | +``` |
| 142 | + |
| 143 | +--- |
| 144 | + |
| 145 | +## Driver: `makeBiconnectedPlanar_driver.cpp` |
| 146 | + |
| 147 | +``` |
| 148 | +FILE: src/planar/makeBiconnectedPlanar_driver.cpp |
| 149 | +
|
| 150 | +FUNCTION pgr_do_makeBiconnectedPlanar( |
| 151 | + edges_sql, |
| 152 | + return_tuples, // OUT: II_t_rt** |
| 153 | + return_count, // OUT: size_t* |
| 154 | + log_msg, |
| 155 | + notice_msg, |
| 156 | + err_msg |
| 157 | +): |
| 158 | +
|
| 159 | + TRY: |
| 160 | + // Get edges from SQL |
| 161 | + hint = edges_sql |
| 162 | + edges = get_edges(edges_sql, true, false) |
| 163 | + // true = normal, false = no reverse_cost required |
| 164 | +
|
| 165 | + IF edges.empty(): |
| 166 | + *notice_msg = "No edges found" |
| 167 | + RETURN |
| 168 | +
|
| 169 | + hint = nullptr |
| 170 | +
|
| 171 | + // Build undirected graph |
| 172 | + undigraph = pgrouting::UndirectedGraph() |
| 173 | + undigraph.insert_edges(edges) |
| 174 | +
|
| 175 | + // Call wrapper |
| 176 | + fn = Pgr_makeBiconnectedPlanar<UndirectedGraph>() |
| 177 | + results = fn.makeBiconnectedPlanar(undigraph) |
| 178 | +
|
| 179 | + // Return results |
| 180 | + IF results.empty(): |
| 181 | + // Already biconnected — return 0 rows (not an error) |
| 182 | + *return_tuples = NULL |
| 183 | + *return_count = 0 |
| 184 | + RETURN |
| 185 | +
|
| 186 | + *return_tuples = pgr_alloc(results.size(), *return_tuples) |
| 187 | + FOR i = 0 to results.size(): |
| 188 | + *(*return_tuples + i) = results[i] |
| 189 | + *return_count = results.size() |
| 190 | +
|
| 191 | + // Error handling: standard pgRouting pattern |
| 192 | + CATCH AssertFailedException: |
| 193 | + *err_msg = except.what() |
| 194 | + CATCH const std::string&: // <-- This catches our "Graph is not planar" etc. |
| 195 | + *err_msg = to_pg_msg(ex) |
| 196 | + *log_msg = hint ? to_pg_msg(hint) : to_pg_msg(log) |
| 197 | + CATCH std::exception: |
| 198 | + *err_msg = except.what() |
| 199 | + CATCH ...: |
| 200 | + *err_msg = "Caught unknown exception!" |
| 201 | +``` |
| 202 | + |
| 203 | +--- |
| 204 | + |
| 205 | +## Error Reporting |
| 206 | + |
| 207 | +All errors use `throw std::string(...)` — the standard pgRouting pattern. |
| 208 | +The driver's `catch (const std::string &ex)` block converts this to a PostgreSQL error message |
| 209 | +via `to_pg_msg()`. |
| 210 | + |
| 211 | +| Condition | Error Message | |
| 212 | +|-----------|---------------| |
| 213 | +| Graph not planar | `"Graph is not planar."` | |
| 214 | +| Graph not connected | `"Graph is not connected. Use pgr_makeConnected first."` | |
| 215 | +| No edges | Notice: `"No edges found"` (not an error) | |
| 216 | +| Already biconnected | Returns 0 rows (natural output, not an error) | |
| 217 | + |
| 218 | +**Note on connectivity check:** The proposal specifies checking connectivity as a precondition. |
| 219 | +Implementation decision: either check explicitly in the wrapper using `connected_components()`, |
| 220 | +or let `make_biconnected_planar` fail naturally on disconnected input. Will verify which |
| 221 | +approach is cleaner during Week 1 coding. |
| 222 | + |
| 223 | +--- |
| 224 | + |
| 225 | +## Key Design Decisions |
| 226 | + |
| 227 | +1. **Non-mutating visitor:** Correct for `makeBiconnectedPlanar`. The algorithm identifies all |
| 228 | + edges in a single pass over articulation points without re-reading adjacency structure. |
| 229 | + (See roadmap's visitor design table for comparison with `makeMaximalPlanar`.) |
| 230 | + |
| 231 | +2. **EdgeIndexMap constructed locally:** `std::map<E, int>` wrapped in |
| 232 | + `boost::associative_property_map`. pgRouting's `UndirectedGraph` does NOT have interior |
| 233 | + `edge_index_t` property (confirmed from `base_graph.hpp` line 186-190 and `basic_edge.hpp`). |
| 234 | + |
| 235 | +3. **Vertex ID mapping via `graph[descriptor].id`:** Provided by `Pgr_base_graph` through |
| 236 | + the `Basic_vertex` struct. No manual vertex ID map needed. |
| 237 | + |
| 238 | +4. **Embedding computed internally:** Always runs `boyer_myrvold_planarity_test` with embedding |
| 239 | + parameter. The existing `boyerMyrvold.hpp` only does boolean check without embedding — |
| 240 | + we call the Boost function directly with the embedding parameter. |
| 241 | + |
| 242 | +5. **Return type `II_t_rt`:** Same as `pgr_makeConnected`. Two fields: `d1` (start_vid), |
| 243 | + `d2` (end_vid). The `seq` column is added by the C SRF wrapper. |
0 commit comments