From 1b89301922cf28fc334e26e56824b07033d00507 Mon Sep 17 00:00:00 2001 From: jalving Date: Tue, 11 Nov 2025 13:38:32 -0800 Subject: [PATCH] add doc strings, bump data structures, sort neighbors --- .github/workflows/CI.yml | 2 +- Project.toml | 2 +- src/GraphOptInterface.jl | 6 ++ src/GraphViews/bipartite.jl | 95 ++++++++++++++++++++ src/GraphViews/hypergraph.jl | 160 ++++++++++++++++++++++++++++++++- src/hypergraph_interface.jl | 69 +++++++++++++- src/optigraph.jl | 168 +++++++++++++++++++++++++++++++++++ 7 files changed, 495 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3b240b1..092383b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -24,7 +24,7 @@ jobs: matrix: version: - '1.6' - - '1.8' + - '1.12' - 'nightly' os: - ubuntu-latest diff --git a/Project.toml b/Project.toml index 930b4bb..535c544 100644 --- a/Project.toml +++ b/Project.toml @@ -12,7 +12,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] -DataStructures = "0.18" +DataStructures = "0.18,0.19" Lazy="0.15" Graphs = "1.8" MathOptInterface = "1.6" diff --git a/src/GraphOptInterface.jl b/src/GraphOptInterface.jl index 0508793..dc92ded 100644 --- a/src/GraphOptInterface.jl +++ b/src/GraphOptInterface.jl @@ -15,6 +15,12 @@ Abstract supertype for block-structure-exploiting optimizers. """ abstract type AbstractGraphOptimizer <: MOI.AbstractOptimizer end +""" + supports_graph_interface(optimizer::MOI.AbstractOptimizer) + +Check if the given optimizer supports the graph interface. Returns `false` for standard +MOI optimizers and `true` for `AbstractGraphOptimizer` subtypes. +""" function supports_graph_interface(::MOI.AbstractOptimizer) return false end diff --git a/src/GraphViews/bipartite.jl b/src/GraphViews/bipartite.jl index c0e50c5..f74b213 100644 --- a/src/GraphViews/bipartite.jl +++ b/src/GraphViews/bipartite.jl @@ -8,10 +8,21 @@ mutable struct BipartiteGraph <: Graphs.AbstractGraph{Int64} vertexset1::Vector{Int64} vertexset2::Vector{Int64} end +""" + BipartiteGraph() + +Construct an empty `BipartiteGraph` with no vertices or edges. +""" function BipartiteGraph() return BipartiteGraph(Graphs.Graph(), Vector{Int64}(), Vector{Int64}()) end +""" + Graphs.add_vertex!(bgraph::BipartiteGraph; bipartite=1) + +Add a vertex to the bipartite graph. The `bipartite` keyword argument specifies +which vertex set to add the vertex to (1 or 2). Returns the success status. +""" function Graphs.add_vertex!(bgraph::BipartiteGraph; bipartite=1) added = Graphs.add_vertex!(bgraph.graph) vertex = Graphs.nv(bgraph.graph) @@ -24,34 +35,91 @@ function Graphs.add_vertex!(bgraph::BipartiteGraph; bipartite=1) return added end +""" + Graphs.add_edge!(bgraph::BipartiteGraph, from::Int64, to::Int64) + +Add an edge between vertices `from` and `to`. Enforces bipartite structure by +requiring that the vertices be in different vertex sets. +""" function Graphs.add_edge!(bgraph::BipartiteGraph, from::Int64, to::Int64) length(intersect((from, to), bgraph.vertexset1)) == 1 || error("$from and $to must be in separate vertex sets") return Graphs.add_edge!(bgraph.graph, from, to) end +""" + Graphs.edges(bgraph::BipartiteGraph) + +Return an iterator over all edges in the bipartite graph. +""" Graphs.edges(bgraph::BipartiteGraph) = Graphs.edges(bgraph.graph) +""" + Graphs.edgetype(bgraph::BipartiteGraph) + +Return the edge type for a bipartite graph (`SimpleEdge{Int64}`). +""" Graphs.edgetype(bgraph::BipartiteGraph) = Graphs.SimpleGraphs.SimpleEdge{Int64} +""" + Graphs.has_edge(bgraph::BipartiteGraph, from::Int64, to::Int64) + +Check if the bipartite graph has an edge from `from` to `to`. +""" function Graphs.has_edge(bgraph::BipartiteGraph, from::Int64, to::Int64) return Graphs.has_edge(bgraph.graph, from, to) end +""" + Graphs.has_vertex(bgraph::BipartiteGraph, v::Integer) + +Check if the bipartite graph contains vertex `v`. +""" function Graphs.has_vertex(bgraph::BipartiteGraph, v::Integer) return Graphs.has_vertex(bgraph.graph, v) end +""" + Graphs.is_directed(bgraph::BipartiteGraph) + +Bipartite graphs are undirected. Always returns `false`. +""" Graphs.is_directed(bgraph::BipartiteGraph) = false +""" + Graphs.is_directed(::Type{BipartiteGraph}) + +Bipartite graphs are undirected. Always returns `false`. +""" Graphs.is_directed(::Type{BipartiteGraph}) = false +""" + Graphs.ne(bgraph::BipartiteGraph) + +Return the number of edges in the bipartite graph. +""" Graphs.ne(bgraph::BipartiteGraph) = Graphs.ne(bgraph.graph) +""" + Graphs.nv(bgraph::BipartiteGraph) + +Return the number of vertices in the bipartite graph. +""" Graphs.nv(bgraph::BipartiteGraph) = Graphs.nv(bgraph.graph) +""" + Graphs.vertices(bgraph::BipartiteGraph) + +Return a vector of all vertices in the bipartite graph. +""" Graphs.vertices(bgraph::BipartiteGraph) = Graphs.vertices(bgraph.graph) +""" + Graphs.adjacency_matrix(bgraph::BipartiteGraph) + +Return the adjacency matrix of the bipartite graph. Rows correspond to vertices +in the first set, columns correspond to vertices in the second set. +""" function Graphs.adjacency_matrix(bgraph::BipartiteGraph) n_v1 = length(bgraph.vertexset1) n_v2 = length(bgraph.vertexset2) @@ -62,6 +130,18 @@ function Graphs.adjacency_matrix(bgraph::BipartiteGraph) return A end +""" + identify_separators(bgraph::BipartiteGraph, partitions::Vector; cut_selector=Graphs.degree) + +Identify separator elements (cut vertices or cut edges) that separate the given `partitions`. +The `cut_selector` parameter determines how to assign boundary elements to cuts. It can be: +- A function (e.g., `Graphs.degree`) that compares element degrees +- `:vertex` to always assign vertices as separators +- `:edge` to always assign edges as separators + +Returns a tuple `(partition_elements, cross_elements)` where `partition_elements` are the +elements local to each partition and `cross_elements` are the separator elements. +""" function identify_separators( bgraph::BipartiteGraph, partitions::Vector; cut_selector=Graphs.degree ) @@ -123,6 +203,21 @@ function identify_separators( return partition_elements, cross_elements end +""" + induced_elements(bgraph::BipartiteGraph, partitions::Vector; cut_selector=Graphs.degree) + +Get the induced elements for each partition (i.e., elements local to each partition, +excluding separators). This is a convenience function that returns only the first element +of `identify_separators`. + +# Arguments +- `bgraph::BipartiteGraph`: The bipartite graph +- `partitions::Vector`: Vector of partitions +- `cut_selector=Graphs.degree`: Selector for determining cut assignment (function, `:vertex`, or `:edge`) + +# Returns +A vector of vectors, where each inner vector contains the induced elements for that partition. +""" function induced_elements( bgraph::BipartiteGraph, partitions::Vector; cut_selector=Graphs.degree ) diff --git a/src/GraphViews/hypergraph.jl b/src/GraphViews/hypergraph.jl index fd76427..24480fa 100644 --- a/src/GraphViews/hypergraph.jl +++ b/src/GraphViews/hypergraph.jl @@ -1,11 +1,32 @@ +""" + HyperNode + +Type alias for a hypernode, represented as an `Int64`. +""" const HyperNode = Int64 +""" + HyperEdge + +A hyperedge connecting multiple hypernodes in a hypergraph. +Contains a set of vertices (hypernodes) that the edge connects. +""" struct HyperEdge <: Graphs.AbstractEdge{Int64} vertices::Set{HyperNode} end +""" + HyperEdge(t::Vector{HyperNode}) + +Construct a `HyperEdge` from a vector of hypernodes. +""" HyperEdge(t::Vector{HyperNode}) = HyperEdge(Set(t)) +""" + HyperEdge(t::HyperNode...) + +Construct a `HyperEdge` from a variable number of hypernode arguments. +""" HyperEdge(t::HyperNode...) = HyperEdge(Set(collect(t))) """ @@ -19,6 +40,11 @@ mutable struct HyperGraph <: Graphs.AbstractGraph{Int64} hyperedges::OrderedDict{Set{HyperNode},Int64} node_map::OrderedDict{HyperNode,Vector{HyperEdge}} end +""" + HyperGraph() + +Construct an empty `HyperGraph` with no vertices or hyperedges. +""" function HyperGraph() return HyperGraph( OrderedSet{HyperNode}(), @@ -28,29 +54,64 @@ function HyperGraph() ) end +""" + Base.getindex(hypergraph::HyperGraph, node::HyperNode) + +Get the index of a hypernode (returns the node itself as the index). +""" function Base.getindex(hypergraph::HyperGraph, node::HyperNode) return node end +""" + Base.getindex(hypergraph::HyperGraph, edge::HyperEdge) + +Get the integer index of a hyperedge in the hypergraph. +""" function Base.getindex(hypergraph::HyperGraph, edge::HyperEdge) return hypergraph.hyperedges[edge.vertices] end +""" + Base.reverse(e::HyperEdge) + +Hyperedges do not support reversal. Throws an error. +""" Base.reverse(e::HyperEdge) = error("`HyperEdge` does not support reverse.") +""" + Base.:(==)(h1::HyperEdge, h2::HyperEdge) + +Check if two hyperedges are equal by comparing their vertex sets. +""" function Base.:(==)(h1::HyperEdge, h2::HyperEdge) return collect(h1.vertices) == collect(h2.vertices) end +""" + get_hyperedge(hypergraph::HyperGraph, edge_index::Int64) + +Get the hyperedge corresponding to the given integer index. +""" function get_hyperedge(hypergraph::HyperGraph, edge_index::Int64) return hypergraph.hyperedge_map[edge_index] end +""" + get_hyperedge(hypergraph::HyperGraph, hypernodes::Set) + +Get the hyperedge connecting the given set of hypernodes. +""" function get_hyperedge(hypergraph::HyperGraph, hypernodes::Set) edge_index = hypergraph.hyperedges[hypernodes] return hypergraph.hyperedge_map[edge_index] end +""" + Graphs.add_vertex!(hypergraph::HyperGraph) + +Add a new vertex (hypernode) to the hypergraph. Returns the new hypernode index. +""" function Graphs.add_vertex!(hypergraph::HyperGraph) # test for overflow (Graphs.nv(hypergraph) + one(Int) <= Graphs.nv(hypergraph)) && return false @@ -61,6 +122,12 @@ function Graphs.add_vertex!(hypergraph::HyperGraph) return hypernode end +""" + Graphs.add_edge!(hypergraph::HyperGraph, hypernodes::HyperNode...) + +Add a hyperedge connecting the given hypernodes. If the hyperedge already exists, +returns the existing hyperedge. Requires at least 2 hypernodes. +""" function Graphs.add_edge!(hypergraph::HyperGraph, hypernodes::HyperNode...) @assert length(hypernodes) > 1 hypernodes = Set(collect(hypernodes)) @@ -79,41 +146,107 @@ function Graphs.add_edge!(hypergraph::HyperGraph, hypernodes::HyperNode...) end end +""" + Graphs.edges(hypergraph::HyperGraph) + +Return an iterator over all hyperedges in the hypergraph. +""" Graphs.edges(hypergraph::HyperGraph) = values(hypergraph.hyperedge_map) +""" + Graphs.edgetype(graph::HyperGraph) + +Return the edge type for a hypergraph (`HyperEdge`). +""" Graphs.edgetype(graph::HyperGraph) = HyperEdge +""" + Graphs.has_edge(graph::HyperGraph, edge::HyperEdge) + +Check if the hypergraph contains the given hyperedge. +""" function Graphs.has_edge(graph::HyperGraph, edge::HyperEdge) return edge in values(graph.hyperedge_map) end +""" + Graphs.has_edge(graph::HyperGraph, hypernodes::Set{HyperNode}) + +Check if the hypergraph has a hyperedge connecting the given set of hypernodes. +""" function Graphs.has_edge(graph::HyperGraph, hypernodes::Set{HyperNode}) return haskey(graph.hyperedges, hypernodes) end +""" + Graphs.has_vertex(graph::HyperGraph, v::Integer) + +Check if the hypergraph contains the given vertex. +""" Graphs.has_vertex(graph::HyperGraph, v::Integer) = v in Graphs.vertices(graph) +""" + Graphs.is_directed(graph::HyperGraph) + +Hypergraphs are undirected. Always returns `false`. +""" Graphs.is_directed(graph::HyperGraph) = false +""" + Graphs.is_directed(::Type{HyperGraph}) + +Hypergraphs are undirected. Always returns `false`. +""" Graphs.is_directed(::Type{HyperGraph}) = false +""" + Graphs.ne(graph::HyperGraph) + +Return the number of hyperedges in the hypergraph. +""" Graphs.ne(graph::HyperGraph) = length(graph.hyperedge_map) +""" + Graphs.nv(graph::HyperGraph) + +Return the number of vertices (hypernodes) in the hypergraph. +""" Graphs.nv(graph::HyperGraph) = length(graph.vertices) +""" + Graphs.vertices(graph::HyperGraph) + +Return a vector of all vertices in the hypergraph. +""" Graphs.vertices(graph::HyperGraph) = collect(graph.vertices) +""" + Graphs.vertices(hyperedge::HyperEdge) + +Return a vector of all vertices connected by the hyperedge. +""" Graphs.vertices(hyperedge::HyperEdge) = collect(hyperedge.vertices) +""" + Graphs.degree(g::HyperGraph, v::Int) + +Return the degree of vertex `v` (the number of unique neighbors). +""" Graphs.degree(g::HyperGraph, v::Int) = length(Graphs.all_neighbors(g, v)) +""" + Graphs.all_neighbors(g::HyperGraph, node::HyperNode) + +Return all neighbor nodes of the given `node` in sorted order. Neighbors are nodes +that share at least one hyperedge with the given node. +""" function Graphs.all_neighbors(g::HyperGraph, node::HyperNode) hyperedges = g.node_map[node] #incident hyperedges to the hypernode neighbors = HyperNode[] for edge in hyperedges append!(neighbors, [vert for vert in edge.vertices if vert != node]) end - return unique(neighbors) + return sort!(unique(neighbors)) end """ @@ -352,9 +485,10 @@ function induced_elements(hypergraph::HyperGraph, partitions::Vector{Vector{Hype end """ - neighborhood(g::HyperGraph,nodes::Vector{OptiNode},distance::Int64) + neighborhood(g::HyperGraph, nodes::Vector{HyperNode}, distance::Int64) -Retrieve the neighborhood within `distance` of `nodes`. Returns a vector of the original vertices and added vertices +Retrieve the neighborhood within `distance` of `nodes`. Returns a vector containing +the original vertices and all vertices within the specified distance. """ function neighborhood(g::HyperGraph, nodes::Vector{HyperNode}, distance::Int64) V = collect(nodes) @@ -371,6 +505,13 @@ function neighborhood(g::HyperGraph, nodes::Vector{HyperNode}, distance::Int64) return nbr end +""" + expand(g::HyperGraph, nodes::Vector{HyperNode}, distance::Int64) + +Expand the given `nodes` by the specified `distance`. Returns a tuple of +`(new_nodes, new_edges)` where `new_nodes` includes all nodes within distance +and `new_edges` are the induced hyperedges on those nodes. +""" function expand(g::HyperGraph, nodes::Vector{HyperNode}, distance::Int64) new_nodes = neighborhood(g, nodes, distance) new_edges = induced_edges(g, new_nodes) @@ -378,14 +519,25 @@ function expand(g::HyperGraph, nodes::Vector{HyperNode}, distance::Int64) end #################################### -#Print Functions +# Print Functions #################################### + +""" + string(graph::HyperGraph) + +Return a string representation of the hypergraph showing vertex and edge counts. +""" function string(graph::HyperGraph) return "Hypergraph: " * "($(nv(graph)) , $(ne(graph)))" end print(io::IO, graph::HyperGraph) = print(io, string(graph)) show(io::IO, graph::HyperGraph) = print(io, graph) +""" + string(edge::HyperEdge) + +Return a string representation of the hyperedge showing its vertices. +""" function string(edge::HyperEdge) return "HyperEdge: " * "$(collect(edge.vertices))" end diff --git a/src/hypergraph_interface.jl b/src/hypergraph_interface.jl index 966de31..f5d6206 100644 --- a/src/hypergraph_interface.jl +++ b/src/hypergraph_interface.jl @@ -12,6 +12,11 @@ struct HyperMap node_to_hypernode_map::OrderedDict{Node,HyperNode} edge_to_hyperedge_map::OrderedDict{Edge,HyperEdge} end +""" + HyperMap(graph::OptiGraph, hypergraph::HyperGraph) + +Construct a new `HyperMap` with empty mappings between the given `graph` and `hypergraph`. +""" function HyperMap(graph::OptiGraph, hypergraph::HyperGraph) return HyperMap( graph, @@ -23,38 +28,85 @@ function HyperMap(graph::OptiGraph, hypergraph::HyperGraph) ) end +""" + Base.getindex(hyper_map::HyperMap, vertex::HyperNode) + +Get the `Node` that corresponds to the given `vertex` in the hyper map. +""" function Base.getindex(hyper_map::HyperMap, vertex::HyperNode) return hyper_map.hypernode_to_node_map[vertex] end +""" + Base.setindex!(hyper_map::HyperMap, vertex::HyperNode, node::Node) + +Set the mapping from `vertex` to `node` in the hyper map. +""" function Base.setindex!(hyper_map::HyperMap, vertex::HyperNode, node::Node) return hyper_map.hypernode_to_node_map[vertex] = node end +""" + Base.getindex(hyper_map::HyperMap, node::Node) + +Get the `HyperNode` that corresponds to the given `node` in the hyper map. +""" function Base.getindex(hyper_map::HyperMap, node::Node) return hyper_map.node_to_hypernode_map[node] end +""" + Base.setindex!(hyper_map::HyperMap, node::Node, vertex::HyperNode) + +Set the mapping from `node` to `vertex` in the hyper map. +""" function Base.setindex!(hyper_map::HyperMap, node::Node, vertex::HyperNode) return hyper_map.node_to_hypernode_map[node] = vertex end +""" + Base.getindex(hyper_map::HyperMap, hyperedge::HyperEdge) + +Get the `Edge` that corresponds to the given `hyperedge` in the hyper map. +""" function Base.getindex(hyper_map::HyperMap, hyperedge::HyperEdge) return hyper_map.hyperedge_to_edge_map[hyperedge] end +""" + Base.setindex!(hyper_map::HyperMap, hyperedge::HyperEdge, edge::Edge) + +Set the mapping from `hyperedge` to `edge` in the hyper map. +""" function Base.setindex!(hyper_map::HyperMap, hyperedge::HyperEdge, edge::Edge) return hyper_map.hyperedge_to_edge_map[hyperedge] = edge end +""" + Base.getindex(hyper_map::HyperMap, edge::Edge) + +Get the `HyperEdge` that corresponds to the given `edge` in the hyper map. +""" function Base.getindex(hyper_map::HyperMap, edge::Edge) return hyper_map.edge_to_hyperedge_map[edge] end +""" + Base.setindex!(hyper_map::HyperMap, edge::Edge, hyperedge::HyperEdge) + +Set the mapping from `edge` to `hyperedge` in the hyper map. +""" function Base.setindex!(hyper_map::HyperMap, edge::Edge, hyperedge::HyperEdge) return hyper_map.edge_to_hyperedge_map[edge] = hyperedge end +""" + build_hypergraph_map(graph::OptiGraph) + +Build a `HyperMap` that maps the given `graph` to a `HyperGraph` representation. +Creates hypernodes for each node and hyperedges for each edge in the graph. +Returns the complete mapping. +""" function build_hypergraph_map(graph::OptiGraph) hypergraph = HyperGraph() hyper_map = HyperMap(graph, hypergraph) @@ -75,7 +127,7 @@ function build_hypergraph_map(graph::OptiGraph) end """ - get_mapped_nodes(hyper_map::HyperMap, nodes::Vector{Node}) + get_mapped_nodes(hyper_map::HyperMap, nodes::Vector{Node}) Get the hypernode elements that correspond to the supplied optigraph `nodes`. """ @@ -83,14 +135,29 @@ function get_mapped_nodes(hyper_map::HyperMap, nodes::Vector{Node}) return getindex.(Ref(hyper_map.node_to_hypernode_map), nodes) end +""" + get_mapped_nodes(hyper_map::HyperMap, nodes::Vector{HyperNode}) + +Get the optigraph `Node` elements that correspond to the supplied hypergraph `nodes`. +""" function get_mapped_nodes(hyper_map::HyperMap, nodes::Vector{HyperNode}) return getindex.(Ref(hyper_map.hypernode_to_node_map), nodes) end +""" + get_mapped_edges(hyper_map::HyperMap, edges::Vector{Edge}) + +Get the hypergraph `HyperEdge` elements that correspond to the supplied optigraph `edges`. +""" function get_mapped_edges(hyper_map::HyperMap, edges::Vector{Edge}) return getindex.(Ref(hyper_map.edge_to_hyperedge_map), edges) end +""" + get_mapped_edges(hyper_map::HyperMap, edges::Vector{HyperEdge}) + +Get the optigraph `Edge` elements that correspond to the supplied hypergraph `edges`. +""" function get_mapped_edges(hyper_map::HyperMap, edges::Vector{HyperEdge}) return getindex.(Ref(hyper_map.hyperedge_to_edge_map), edges) end diff --git a/src/optigraph.jl b/src/optigraph.jl index 33c1705..9623db7 100644 --- a/src/optigraph.jl +++ b/src/optigraph.jl @@ -1,9 +1,19 @@ +""" + GraphIndex + +A unique identifier for an `OptiGraph` using a Symbol value. +""" struct GraphIndex value::Symbol end ### Node +""" + NodeIndex + +A unique identifier for a `Node` using an integer value. +""" struct NodeIndex value::Int64 end @@ -19,15 +29,31 @@ mutable struct Node moi_model::MOIU.UniversalFallback{MOIU.Model{Float64}} nonlinear_model::Union{Nothing,MOI.Nonlinear.Model} end +""" + Node(graph_index::GraphIndex, node_index::NodeIndex) + +Construct a new `Node` with the given graph and node indices. Initializes an empty +MOI model for storing variables and constraints. +""" function Node(graph_index::GraphIndex, node_index::NodeIndex) moi_model = MOIU.UniversalFallback(MOIU.Model{Float64}()) return Node(graph_index, node_index, moi_model, nothing) end +""" + node_index(node::Node)::NodeIndex + +Return the `NodeIndex` of the given `node`. +""" function node_index(node::Node)::NodeIndex return node.index end +""" + raw_index(node::Node) + +Return the raw integer value of the node's index. +""" function raw_index(node::Node) return node.index.value end @@ -68,14 +94,31 @@ end ### Edges +""" + EdgeIndex + +A unique identifier for an `Edge` using an integer value. +""" struct EdgeIndex value::Int64 end +""" + EdgeIndexMap + +A bidirectional mapping between edge variables and their corresponding node variables. +Contains mappings from edge variables to `(Node, MOI.VariableIndex)` tuples and vice versa. +""" struct EdgeIndexMap edge_to_node_map::OrderedDict{MOI.VariableIndex,Tuple{Node,MOI.VariableIndex}} node_to_edge_map::OrderedDict{Tuple{Node,MOI.VariableIndex},MOI.VariableIndex} end + +""" + EdgeIndexMap() + +Construct an empty `EdgeIndexMap` with no variable mappings. +""" function EdgeIndexMap() edge_to_node_map = OrderedDict{MOI.VariableIndex,Tuple{Node,MOI.VariableIndex}}() node_to_edge_map = OrderedDict{Tuple{Node,MOI.VariableIndex},MOI.VariableIndex}() @@ -97,6 +140,12 @@ mutable struct Edge moi_model::MOIU.UniversalFallback{MOIU.Model{Float64}} nonlinear_model::Union{Nothing,MOI.Nonlinear.Model} end +""" + Edge(graph_index::GraphIndex, edge_index::EdgeIndex, nodes::NTuple{N,Node}) + +Construct a new `Edge` connecting the given tuple of `nodes`. Initializes an empty +MOI model and edge index map for storing coupling constraints. +""" function Edge( graph_index::GraphIndex, edge_index::EdgeIndex, nodes::NTuple{N,Node} where {N} ) @@ -106,10 +155,20 @@ function Edge( ) end +""" + edge_index(edge::Edge)::EdgeIndex + +Return the `EdgeIndex` of the given `edge`. +""" function edge_index(edge::Edge)::EdgeIndex return edge.index end +""" + raw_index(edge::Edge) + +Return the raw integer value of the edge's index. +""" function raw_index(edge::Edge) return edge.index.value end @@ -126,6 +185,12 @@ end ### MOI Edge Functions +""" + MOI.add_variable(edge::Edge, node::Node, variable::MOI.VariableIndex) + +Add a variable to the `edge` that maps to the given `variable` on the specified `node`. +Returns the edge's variable index for use in edge constraints. +""" function MOI.add_variable(edge::Edge, node::Node, variable::MOI.VariableIndex) edge_variable = MOI.add_variable(edge.moi_model) edge.index_map.edge_to_node_map[edge_variable] = (node, variable) @@ -149,14 +214,29 @@ function MOI.Nonlinear.add_constraint( return constraint_index end +""" + get_nodes(edge::Edge) + +Return a vector of all nodes connected by the given `edge`. +""" function get_nodes(edge::Edge) return collect(edge.nodes) end +""" + get_edge_variable(edge::Edge, node::Node, variable::MOI.VariableIndex) + +Get the edge variable index that corresponds to the given `node` and `variable` pair. +""" function get_edge_variable(edge::Edge, node::Node, variable::MOI.VariableIndex) return edge.index_map.node_to_edge_map[(node, variable)] end +""" + get_node_variable(edge::Edge, variable::MOI.VariableIndex) + +Get the `(Node, MOI.VariableIndex)` tuple that corresponds to the given edge variable. +""" function get_node_variable(edge::Edge, variable::MOI.VariableIndex) return edge.index_map.edge_to_node_map[variable] end @@ -167,6 +247,23 @@ end ### OptiGraph +""" + OptiGraph + +A hierarchical graph structure for representing optimization problems with block structure. +Contains nodes (representing subproblems), edges (representing coupling constraints), +and subgraphs (for hierarchical decomposition). + +# Fields +- `index::GraphIndex`: Unique identifier for the graph +- `parent::Union{Nothing,OptiGraph}`: Parent graph (if this is a subgraph) +- `nodes::Vector{Node}`: Nodes in this graph +- `edges::Vector{Edge}`: Edges in this graph +- `subgraphs::Vector{OptiGraph}`: Child subgraphs +- `node_by_index::OrderedDict{NodeIndex,Node}`: Lookup map for nodes +- `edge_by_index::OrderedDict{EdgeIndex,Edge}`: Lookup map for edges +- `subgraph_by_index::OrderedDict{GraphIndex,OptiGraph}`: Lookup map for subgraphs +""" mutable struct OptiGraph index::GraphIndex parent::Union{Nothing,OptiGraph} @@ -202,18 +299,38 @@ end Base.print(io::IO, graph::OptiGraph) = Base.print(io, Base.string(graph)) Base.show(io::IO, graph::OptiGraph) = Base.print(io, graph) +""" + graph_index(graph::OptiGraph) + +Return the `GraphIndex` of the given `graph`. +""" function graph_index(graph::OptiGraph) return graph.index end +""" + raw_index(graph::OptiGraph) + +Return the raw Symbol value of the graph's index. +""" function raw_index(graph::OptiGraph) return graph.index.value end +""" + num_nodes(graph::OptiGraph) + +Return the number of nodes directly contained in `graph` (not including subgraph nodes). +""" function num_nodes(graph::OptiGraph) return length(graph.nodes) end +""" + num_all_nodes(graph::OptiGraph) + +Return the total number of nodes in `graph` including all nodes in subgraphs recursively. +""" function num_all_nodes(graph::OptiGraph) num = num_nodes(graph) for subgraph in get_subgraphs(graph) @@ -222,6 +339,11 @@ function num_all_nodes(graph::OptiGraph) return num end +""" + all_nodes(graph::OptiGraph) + +Return all nodes in `graph` including nodes from all subgraphs recursively. +""" function all_nodes(graph::OptiGraph) nodes = Node[] append!(nodes, graph.nodes) @@ -233,6 +355,11 @@ function all_nodes(graph::OptiGraph) return nodes end +""" + num_edges(graph::OptiGraph) + +Return the number of edges directly contained in `graph` (not including subgraph edges). +""" function num_edges(graph::OptiGraph) return length(graph.edges) end @@ -279,10 +406,21 @@ function add_node(graph::OptiGraph)::Node return node end +""" + get_nodes(graph::OptiGraph) + +Return the vector of nodes directly contained in `graph` (not including subgraph nodes). +""" function get_nodes(graph::OptiGraph) return graph.nodes end +""" + get_nodes_to_depth(graph::OptiGraph, depth::Int=0) + +Return nodes in `graph` up to the specified `depth` of subgraphs. A depth of 0 returns +only nodes in the current graph, depth of 1 includes immediate subgraph nodes, etc. +""" function get_nodes_to_depth(graph::OptiGraph, depth::Int=0) nodes = graph.nodes if depth > 0 @@ -309,10 +447,20 @@ function add_edge(graph::OptiGraph, nodes::NTuple{N,Node})::Edge where {N} return edge end +""" + get_edges(graph::OptiGraph) + +Return the vector of edges directly contained in `graph` (not including subgraph edges). +""" function get_edges(graph::OptiGraph) return graph.edges end +""" + all_edges(graph::OptiGraph) + +Return all edges in `graph` including edges from all subgraphs recursively. +""" function all_edges(graph::OptiGraph) edges = graph.edges if !isempty(graph.edges) @@ -336,10 +484,20 @@ function add_subgraph(graph::OptiGraph) return subgraph end +""" + get_subgraphs(graph::OptiGraph) + +Return the vector of immediate subgraphs contained in `graph`. +""" function get_subgraphs(graph::OptiGraph) return graph.subgraphs end +""" + all_subgraphs(graph::OptiGraph) + +Return all subgraphs contained in `graph` recursively. +""" function all_subgraphs(graph::OptiGraph) subs = OptiGraph[] append!(subs, graph.subgraphs) @@ -349,10 +507,20 @@ function all_subgraphs(graph::OptiGraph) return subs end +""" + num_subgraphs(graph::OptiGraph) + +Return the number of immediate subgraphs in `graph`. +""" function num_subgraphs(graph::OptiGraph) return length(graph.subgraphs) end +""" + num_all_subgraphs(graph::OptiGraph) + +Return the total number of subgraphs in `graph` recursively. +""" function num_all_subgraphs(graph::OptiGraph) num = num_subgraphs(graph) for subgraph in get_subgraphs(graph)