@@ -505,6 +505,15 @@ class Graph {
505505 return v;
506506 }
507507
508+ std::optional<VertexDesc> find_vertex_by_id (const NodeID& id) const {
509+ for (auto [v, vend] = boost::vertices (g); v != vend; ++v) {
510+ if (boost::get (vertex_name_map, *v) == id) {
511+ return *v;
512+ }
513+ }
514+ return std::nullopt ;
515+ }
516+
508517 std::size_t get_edge_id (EdgeDesc e) const {
509518 return boost::get (edge_id_map, e);
510519 }
@@ -549,9 +558,14 @@ class Graph {
549558
550559 void erase_incident_edge_properties (VertexDesc v) {
551560 std::vector<std::size_t > edge_ids;
552- for (auto [e, eend] = boost::edges (g); e != eend; ++e) {
553- if (boost::source (*e, g) == v || boost::target (*e, g) == v) {
554- edge_ids.push_back (get_edge_id (*e));
561+ for (auto [e, eend] = boost::out_edges (v, g); e != eend; ++e) {
562+ edge_ids.push_back (get_edge_id (*e));
563+ }
564+ if constexpr (Directed) {
565+ for (auto [e, eend] = boost::in_edges (v, g); e != eend; ++e) {
566+ if (boost::source (*e, g) != v) {
567+ edge_ids.push_back (get_edge_id (*e));
568+ }
555569 }
556570 }
557571 for (auto edge_id : edge_ids) {
@@ -1004,8 +1018,8 @@ class Graph {
10041018 * state, erases the vertex, and rebuilds the internal `NodeID` to
10051019 * descriptor and vertex-index maps. Calling this in a tight loop over many
10061020 * node removals can add up to roughly O(V^2 + V*E) total work. Prefer
1007- * building a filtered copy of the graph when you need to drop many
1008- * vertices at once, or use `clear()` to reset a graph entirely.
1021+ * remove_nodes_from() when you need to drop many vertices at once, or use
1022+ * `clear()` to reset a graph entirely.
10091023 *
10101024 * @throws std::runtime_error If the node is not present.
10111025 */
@@ -1025,6 +1039,52 @@ class Graph {
10251039 rebuild_vertex_maps ();
10261040 }
10271041
1042+ /* *
1043+ * @brief Removes a batch of nodes and all of their incident edges.
1044+ *
1045+ * Duplicate node IDs in the input are ignored after the first occurrence.
1046+ * Missing node IDs follow remove_node()'s error policy: the call throws
1047+ * before mutating the graph if any requested node is absent.
1048+ *
1049+ * This rebuilds wrapper-side vertex maps once after the full batch.
1050+ *
1051+ * @throws std::runtime_error If any requested node is not present.
1052+ */
1053+ void remove_nodes_from (const std::vector<NodeID>& nodes) {
1054+ std::vector<NodeID> unique_nodes;
1055+ std::set<NodeID> seen;
1056+ for (const auto & node : nodes) {
1057+ if (seen.insert (node).second ) {
1058+ unique_nodes.push_back (node);
1059+ }
1060+ }
1061+
1062+ for (const auto & node : unique_nodes) {
1063+ if (!has_node (node)) {
1064+ throw std::runtime_error (" Node lookup failed: node not found." );
1065+ }
1066+ }
1067+
1068+ if (unique_nodes.empty ()) {
1069+ return ;
1070+ }
1071+
1072+ invalidate_min_cost_flow_state ();
1073+ for (const auto & node : unique_nodes) {
1074+ auto vertex = find_vertex_by_id (node);
1075+ if (!vertex.has_value ()) {
1076+ continue ;
1077+ }
1078+
1079+ erase_incident_edge_properties (*vertex);
1080+ boost::clear_vertex (*vertex, g);
1081+ boost::remove_vertex (*vertex, g);
1082+ node_properties.erase (node);
1083+ }
1084+
1085+ rebuild_vertex_maps ();
1086+ }
1087+
10281088 /* *
10291089 * @brief Returns the outgoing neighbors of a node.
10301090 *
0 commit comments