blob: 09f81ba7bbdb7b695749df62f4bf2925f0808c0b [file] [log] [blame]
// Copyright ©2019 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package product
import (
"sort"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/internal/ordered"
"gonum.org/v1/gonum/stat/combin"
)
// Node is a product of two graph nodes.
// All graph products return this type directly via relevant graph.Graph method
// call, or indirectly via calls to graph.Edge methods from returned edges.
type Node struct {
UID int64
// A and B hold the nodes from graph a and b in a
// graph product constructed by a product function.
A, B graph.Node
}
// ID implements the graph.Node interface.
func (n Node) ID() int64 { return n.UID }
// Cartesian constructs the Cartesian product of a and b in dst.
//
// The Cartesian product of G₁ and G₂, G₁□G₂ has edges (u₁, u₂)~(v₁, v₂) when
// (u₁=v₁ and u₂~v₂) or (u₁~v₁ and u₂=v₂). The Cartesian product has size m₂n₁+m₁n₂
// where m is the size of the input graphs and n is their order.
func Cartesian(dst graph.Builder, a, b graph.Graph) {
aNodes, bNodes, product := cartesianNodes(a, b)
if len(product) == 0 {
return
}
indexOfA := indexOf(aNodes)
indexOfB := indexOf(bNodes)
for _, p := range product {
dst.AddNode(p)
}
dims := []int{len(aNodes), len(bNodes)}
for i, uA := range aNodes {
for j, uB := range bNodes {
toB := b.From(uB.ID())
for toB.Next() {
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{i, j}, dims)],
product[combin.IdxFor([]int{i, indexOfB[toB.Node().ID()]}, dims)],
))
}
toA := a.From(uA.ID())
for toA.Next() {
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{i, j}, dims)],
product[combin.IdxFor([]int{indexOfA[toA.Node().ID()], j}, dims)],
))
}
}
}
}
// Tensor constructs the Tensor product of a and b in dst.
//
// The Tensor product of G₁ and G₂, G₁⨯G₂ has edges (u₁, u₂)~(v₁, v₂) when
// u₁~v₁ and u₂~v₂. The Tensor product has size 2m₁m₂ where m is the size
// of the input graphs.
func Tensor(dst graph.Builder, a, b graph.Graph) {
aNodes, bNodes, product := cartesianNodes(a, b)
if len(product) == 0 {
return
}
indexOfA := indexOf(aNodes)
indexOfB := indexOf(bNodes)
for _, p := range product {
dst.AddNode(p)
}
dims := []int{len(aNodes), len(bNodes)}
for i, uA := range aNodes {
toA := a.From(uA.ID())
for toA.Next() {
j := indexOfA[toA.Node().ID()]
for k, uB := range bNodes {
toB := b.From(uB.ID())
for toB.Next() {
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{i, k}, dims)],
product[combin.IdxFor([]int{j, indexOfB[toB.Node().ID()]}, dims)],
))
}
}
}
}
}
// Lexicographical constructs the Lexicographical product of a and b in dst.
//
// The Lexicographical product of G₁ and G₂, G₁·G₂ has edges (u₁, u₂)~(v₁, v₂) when
// u₁~v₁ or (u₁=v₁ and u₂~v₂). The Lexicographical product has size m₂n₁+m₁n₂²
// where m is the size of the input graphs and n is their order.
func Lexicographical(dst graph.Builder, a, b graph.Graph) {
aNodes, bNodes, product := cartesianNodes(a, b)
if len(product) == 0 {
return
}
indexOfA := indexOf(aNodes)
indexOfB := indexOf(bNodes)
for _, p := range product {
dst.AddNode(p)
}
dims := []int{len(aNodes), len(bNodes)}
p := make([]int, 2)
for i, uA := range aNodes {
toA := a.From(uA.ID())
for toA.Next() {
j := indexOfA[toA.Node().ID()]
gen := combin.NewCartesianGenerator([]int{len(bNodes), len(bNodes)})
for gen.Next() {
p = gen.Product(p)
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{i, p[0]}, dims)],
product[combin.IdxFor([]int{j, p[1]}, dims)],
))
}
}
for j, uB := range bNodes {
toB := b.From(uB.ID())
for toB.Next() {
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{i, j}, dims)],
product[combin.IdxFor([]int{i, indexOfB[toB.Node().ID()]}, dims)],
))
}
}
}
}
// Strong constructs the Strong product of a and b in dst.
//
// The Strong product of G₁ and G₂, G₁⊠G₂ has edges (u₁, u₂)~(v₁, v₂) when
// (u₁=v₁ and u₂~v₂) or (u₁~v₁ and u₂=v₂) or (u₁~v₁ and u₂~v₂). The Strong
// product has size n₁m₂+n₂m₁+2m₁m₂ where m is the size of the input graphs
// and n is their order.
func Strong(dst graph.Builder, a, b graph.Graph) {
aNodes, bNodes, product := cartesianNodes(a, b)
if len(product) == 0 {
return
}
indexOfA := indexOf(aNodes)
indexOfB := indexOf(bNodes)
for _, p := range product {
dst.AddNode(p)
}
dims := []int{len(aNodes), len(bNodes)}
for i, uA := range aNodes {
for j, uB := range bNodes {
toB := b.From(uB.ID())
for toB.Next() {
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{i, j}, dims)],
product[combin.IdxFor([]int{i, indexOfB[toB.Node().ID()]}, dims)],
))
}
toA := a.From(uA.ID())
for toA.Next() {
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{i, j}, dims)],
product[combin.IdxFor([]int{indexOfA[toA.Node().ID()], j}, dims)],
))
}
}
toA := a.From(uA.ID())
for toA.Next() {
for j, uB := range bNodes {
toB := b.From(uB.ID())
for toB.Next() {
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{i, j}, dims)],
product[combin.IdxFor([]int{indexOfA[toA.Node().ID()], indexOfB[toB.Node().ID()]}, dims)],
))
}
}
}
}
}
// CoNormal constructs the Co-normal product of a and b in dst.
//
// The Co-normal product of G₁ and G₂, G₁*G₂ (or G₁[G₂]) has edges (u₁, u₂)~(v₁, v₂)
// when u₁~v₁ or u₂~v₂. The Co-normal product is non-commutative.
func CoNormal(dst graph.Builder, a, b graph.Graph) {
aNodes, bNodes, product := cartesianNodes(a, b)
if len(product) == 0 {
return
}
indexOfA := indexOf(aNodes)
indexOfB := indexOf(bNodes)
for _, p := range product {
dst.AddNode(p)
}
dims := []int{len(aNodes), len(bNodes)}
p := make([]int, 2)
for i, u := range aNodes {
to := a.From(u.ID())
for to.Next() {
j := indexOfA[to.Node().ID()]
gen := combin.NewCartesianGenerator([]int{len(bNodes), len(bNodes)})
for gen.Next() {
p = gen.Product(p)
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{i, p[0]}, dims)],
product[combin.IdxFor([]int{j, p[1]}, dims)],
))
}
}
}
for i, u := range bNodes {
to := b.From(u.ID())
for to.Next() {
j := indexOfB[to.Node().ID()]
gen := combin.NewCartesianGenerator([]int{len(aNodes), len(aNodes)})
for gen.Next() {
p = gen.Product(p)
dst.SetEdge(dst.NewEdge(
product[combin.IdxFor([]int{p[0], i}, dims)],
product[combin.IdxFor([]int{p[1], j}, dims)],
))
}
}
}
}
// Modular constructs the Modular product of a and b in dst.
//
// The Modular product of G₁ and G₂, G₁◊G₂ has edges (u₁, u₂)~(v₁, v₂)
// when (u₁~v₁ and u₂~v₂) or (u₁≁v₁ and u₂≁v₂), and (u₁≠v₁ and u₂≠v₂).
//
// Modular is O(n^2) time where n is the order of the Cartesian product
// of a and b.
func Modular(dst graph.Builder, a, b graph.Graph) {
_, _, product := cartesianNodes(a, b)
if len(product) == 0 {
return
}
for _, p := range product {
dst.AddNode(p)
}
_, aUndirected := a.(graph.Undirected)
_, bUndirected := b.(graph.Undirected)
_, dstUndirected := dst.(graph.Undirected)
undirected := aUndirected && bUndirected && dstUndirected
n := len(product)
if undirected {
n--
}
for i, u := range product[:n] {
var m int
if undirected {
m = i + 1
}
for _, v := range product[m:] {
if u.A.ID() == v.A.ID() || u.B.ID() == v.B.ID() {
// No self-loops.
continue
}
inA := a.Edge(u.A.ID(), v.A.ID()) != nil
inB := b.Edge(u.B.ID(), v.B.ID()) != nil
if inA == inB {
dst.SetEdge(dst.NewEdge(u, v))
}
}
}
}
// ModularExt constructs the Modular product of a and b in dst with
// additional control over assessing edge agreement.
//
// In addition to the modular product conditions, agree(u₁v₁, u₂v₂) must
// return true when (u₁~v₁ and u₂~v₂) for an edge to be added between
// (u₁, u₂) and (v₁, v₂) in dst. If agree is nil, Modular is called.
//
// ModularExt is O(n^2) time where n is the order of the Cartesian product
// of a and b.
func ModularExt(dst graph.Builder, a, b graph.Graph, agree func(eA, eB graph.Edge) bool) {
if agree == nil {
Modular(dst, a, b)
return
}
_, _, product := cartesianNodes(a, b)
if len(product) == 0 {
return
}
for _, p := range product {
dst.AddNode(p)
}
_, aUndirected := a.(graph.Undirected)
_, bUndirected := b.(graph.Undirected)
_, dstUndirected := dst.(graph.Undirected)
undirected := aUndirected && bUndirected && dstUndirected
n := len(product)
if undirected {
n--
}
for i, u := range product[:n] {
var m int
if undirected {
m = i + 1
}
for _, v := range product[m:] {
if u.A.ID() == v.A.ID() || u.B.ID() == v.B.ID() {
// No self-loops.
continue
}
eA := a.Edge(u.A.ID(), v.A.ID())
eB := b.Edge(u.B.ID(), v.B.ID())
inA := eA != nil
inB := eB != nil
if (inA && inB && agree(eA, eB)) || (!inA && !inB) {
dst.SetEdge(dst.NewEdge(u, v))
}
}
}
}
// cartesianNodes returns the Cartesian product of the nodes in a and b.
func cartesianNodes(a, b graph.Graph) (aNodes, bNodes []graph.Node, product []Node) {
aNodes = lexicalNodes(a)
bNodes = lexicalNodes(b)
lens := []int{len(aNodes), len(bNodes)}
product = make([]Node, combin.Card(lens))
gen := combin.NewCartesianGenerator(lens)
p := make([]int, 2)
for id := int64(0); gen.Next(); id++ {
p = gen.Product(p)
product[id] = Node{UID: id, A: aNodes[p[0]], B: bNodes[p[1]]}
}
return aNodes, bNodes, product
}
// lexicalNodes returns the nodes in g sorted lexically by node ID.
func lexicalNodes(g graph.Graph) []graph.Node {
nodes := graph.NodesOf(g.Nodes())
sort.Sort(ordered.ByID(nodes))
return nodes
}
func indexOf(nodes []graph.Node) map[int64]int {
idx := make(map[int64]int, len(nodes))
for i, n := range nodes {
idx[n.ID()] = i
}
return idx
}