Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ func main() {

fmt.Printf("Maximum flow: %d\n", maxFlow)

// Query node capacities
incoming, _ := f.IncomingCapacityByNode("sink")
fmt.Printf("Incoming capacity to sink: %d\n", incoming)
// Query node flow
flow, _ := f.FlowByNode("a")
fmt.Printf("Outgoing flow from a: %d\n", flow)
}
```

Expand All @@ -54,11 +54,8 @@ Adds a directed edge from `from` to `to` with the specified capacity. Nodes are
### `MaxFlow(source, sink Node) (int, error)`
Computes the maximum flow from the source node to the sink node using Dinic's algorithm.

### `IncomingCapacityByNode(node Node) (int, error)`
Returns the total incoming flow capacity for the specified node.

### `OutgoingCapacityByNode(node Node) (int, error)`
Returns the total outgoing flow capacity for the specified node.
### `FlowByNode(node Node) (int, error)`
Returns the total outgoing flow (used capacity) from the specified node. This method should be called after MaxFlow has been computed.

## License

Expand Down
13 changes: 3 additions & 10 deletions examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,11 @@ func main() {

fmt.Printf("Max flow from 'a' to 'b': %d\n", flow)

// Query incoming and outgoing capacities for specific nodes.
incoming, err := f.IncomingCapacityByNode("b")
// Query the outgoing flow for specific nodes.
outgoing, err := f.FlowByNode("c")
if err != nil {
panic(err)
}

fmt.Printf("Incoming capacity to 'b': %d\n", incoming)

outgoing, err := f.OutgoingCapacityByNode("c")
if err != nil {
panic(err)
}

fmt.Printf("Outgoing capacity from 'c': %d\n", outgoing)
fmt.Printf("Outgoing flow from 'c': %d\n", outgoing)
}
26 changes: 3 additions & 23 deletions flowmo.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,27 +75,7 @@ func (f *Flowmo) MaxFlow(source, sink Node) (int, error) {
return f.network.MaxFlow(sourceIndex, sinkIndex)
}

// IncomingCapacityByNode returns the total incoming flow (used capacity) to the specified node.
// This represents the sum of all flow that has been pushed into the node across all incoming edges.
// This method should be called after MaxFlow has been computed.
//
// Returns the total incoming flow and nil error on success.
// Returns -1 and an error if:
// - Node does not exist (ErrNotFound)
func (f *Flowmo) IncomingCapacityByNode(node Node) (int, error) {
idx, exists := f.indexByNode[node]
if !exists {
return -1, fmt.Errorf(
"node %q: %w",
node,
flowmoerrors.ErrNotFound,
)
}

return f.network.IncomingFlowByNode(idx)
}

// OutgoingCapacityByNode returns the total outgoing flow
// FlowByNode returns the total outgoing flow
// (used capacity) from the specified node.
// This represents the sum of all flow that has been pushed out of
// the node across all outgoing edges.
Expand All @@ -104,7 +84,7 @@ func (f *Flowmo) IncomingCapacityByNode(node Node) (int, error) {
// Returns the total outgoing flow and nil error on success.
// Returns -1 and an error if:
// - Node does not exist (ErrNotFound)
func (f *Flowmo) OutgoingCapacityByNode(node Node) (int, error) {
func (f *Flowmo) FlowByNode(node Node) (int, error) {
idx, exists := f.indexByNode[node]
if !exists {
return -1, fmt.Errorf(
Expand All @@ -114,7 +94,7 @@ func (f *Flowmo) OutgoingCapacityByNode(node Node) (int, error) {
)
}

return f.network.OutgoingFlowByNode(idx)
return f.network.FlowByNode(idx)
}

func (f *Flowmo) addNode(node Node) (int, error) {
Expand Down
31 changes: 6 additions & 25 deletions flowmo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,21 +106,12 @@ func Test_MaxFlow_sinkNotFound(t *testing.T) {
assert.ErrorIs(t, err, flowmoerrors.ErrNotFound)
}

func Test_IncomingCapacityByNode_nodeNotFound(t *testing.T) {
func Test_FlowByNode_nodeNotFound(t *testing.T) {
f := New()

_ = f.AddEdge("a", "b", 10)

_, err := f.IncomingCapacityByNode("x")
assert.ErrorIs(t, err, flowmoerrors.ErrNotFound)
}

func Test_OutgoingCapacityByNode_nodeNotFound(t *testing.T) {
f := New()

_ = f.AddEdge("a", "b", 10)

_, err := f.OutgoingCapacityByNode("x")
_, err := f.FlowByNode("x")
assert.ErrorIs(t, err, flowmoerrors.ErrNotFound)
}

Expand All @@ -144,23 +135,13 @@ func Test_CompleteWorkflow(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 25, maxFlow)

// Verify incoming capacities
incomingB, _ := f.IncomingCapacityByNode("b")
assert.Equal(t, 15, incomingB)

incomingC, _ := f.IncomingCapacityByNode("c")
assert.Equal(t, 15, incomingC)

incomingD, _ := f.IncomingCapacityByNode("d")
assert.Equal(t, 25, incomingD)

// Verify outgoing capacities
outgoingA, _ := f.OutgoingCapacityByNode("a")
// Verify flow
outgoingA, _ := f.FlowByNode("a")
assert.Equal(t, 25, outgoingA)

outgoingB, _ := f.OutgoingCapacityByNode("b")
outgoingB, _ := f.FlowByNode("b")
assert.Equal(t, 15, outgoingB)

outgoingC, _ := f.OutgoingCapacityByNode("c")
outgoingC, _ := f.FlowByNode("c")
assert.Equal(t, 15, outgoingC)
}
27 changes: 1 addition & 26 deletions internal/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,32 +58,7 @@ func (net *Network) MaxFlow(source, sink int) (int, error) {
return maxFlow(net, source, sink)
}

// nolint:gocyclo
func (net *Network) IncomingFlowByNode(node int) (int, error) {
invalidNode := node < 0 || node >= len(net.adj)
if invalidNode {
return -1, fmt.Errorf(
"node %d: %w",
node,
flowmoerrors.ErrInvalidArgument,
)
}

in := 0
for _, edges := range net.adj {
for _, e := range edges {
if e.to != node || e.isResidual() {
continue
}

in += e.initialCapacity - e.capacity
}
}

return in, nil
}

func (net *Network) OutgoingFlowByNode(node int) (int, error) {
func (net *Network) FlowByNode(node int) (int, error) {
invalidNode := node < 0 || node >= len(net.adj)
if invalidNode {
return -1, fmt.Errorf(
Expand Down
47 changes: 6 additions & 41 deletions internal/network/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,45 +66,19 @@ func Test_AddEdge_happyCase(t *testing.T) {
assert.Equal(t, 0, residualEdge.initialCapacity)
}

func Test_IncomingFlowByNode_beforeMaxFlow_shouldReturnZero(t *testing.T) {
func Test_FlowByNode_beforeMaxFlow_shouldReturnZero(t *testing.T) {
net := New()
from := net.AddNode()
to := net.AddNode()
capacity := 10

_ = net.AddEdge(from, to, capacity)

incoming, _ := net.IncomingFlowByNode(to)
assert.Equal(t, 0, incoming)
}

func Test_IncomingFlowByNode_afterMaxFlow_shouldReturnFlow(t *testing.T) {
net := New()
from := net.AddNode()
to := net.AddNode()
capacity := 10

_ = net.AddEdge(from, to, capacity)

_, _ = net.MaxFlow(from, to)

incoming, _ := net.IncomingFlowByNode(to)
assert.Equal(t, capacity, incoming)
}

func Test_OutgoingFlowByNode_beforeMaxFlow_shouldReturnZero(t *testing.T) {
net := New()
from := net.AddNode()
to := net.AddNode()
capacity := 10

_ = net.AddEdge(from, to, capacity)

outgoing, _ := net.OutgoingFlowByNode(from)
outgoing, _ := net.FlowByNode(from)
assert.Equal(t, 0, outgoing)
}

func Test_OutgoingFlowByNode_afterMaxFlow_shouldReturnFlow(t *testing.T) {
func Test_FlowByNode_afterMaxFlow_shouldReturnFlow(t *testing.T) {
net := New()
from := net.AddNode()
to := net.AddNode()
Expand All @@ -114,7 +88,7 @@ func Test_OutgoingFlowByNode_afterMaxFlow_shouldReturnFlow(t *testing.T) {

_, _ = net.MaxFlow(from, to)

outgoing, _ := net.OutgoingFlowByNode(from)
outgoing, _ := net.FlowByNode(from)
assert.Equal(t, capacity, outgoing)
}

Expand All @@ -132,20 +106,11 @@ func Test_AddEdge_selfLoop_shouldWork(t *testing.T) {
assert.Equal(t, 0, flow)
}

func Test_IncomingFlowByNode_invalidNode_shouldReturnError(t *testing.T) {
net := New()
_ = net.AddNode()

incoming, err := net.IncomingFlowByNode(5)
assert.ErrorIs(t, err, flowmoerrors.ErrInvalidArgument)
assert.Equal(t, -1, incoming)
}

func Test_OutgoingFlowByNode_invalidNode_shouldReturnError(t *testing.T) {
func Test_FlowByNode_invalidNode_shouldReturnError(t *testing.T) {
net := New()
_ = net.AddNode()

outgoing, err := net.OutgoingFlowByNode(5)
outgoing, err := net.FlowByNode(5)
assert.ErrorIs(t, err, flowmoerrors.ErrInvalidArgument)
assert.Equal(t, -1, outgoing)
}