Skip to content

Commit d36ac1a

Browse files
committed
Add pseudocode for pgr_makeBiconnectedPlanar and pgr_makeMaximalPlanar wrappers
Community bonding deliverable: pseudocode for both C++ wrapper classes. - pgr_makeBiconnectedPlanar: non-mutating visitor, EdgeIndexMap via std::map + associative_property_map, 4-parameter overload - pgr_makeMaximalPlanar: mutating visitor (calls add_edge), additional VertexIndexMap requirement, 5-parameter overload Key deviation from proposal Section 10.4: makeMaximalPlanar visitor must be mutating to keep graph state consistent for Case B detection in triangulation_visitor::end_face(). Confirmed by makeConnected.hpp mutable graph pattern. Error reporting uses throw std::string(...) matching all existing pgRouting drivers.
1 parent 38f3750 commit d36ac1a

2 files changed

Lines changed: 541 additions & 0 deletions

File tree

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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

Comments
 (0)