blob: 5b1af655c010db848abb3968455cd565c76a5054 [file] [edit]
// 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_test
import (
"fmt"
"os"
"sort"
"text/tabwriter"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/product"
"gonum.org/v1/gonum/graph/simple"
"gonum.org/v1/gonum/graph/topo"
)
// atom is a graph.Node representing an atom in a molecule.
type atom struct {
name string // name is the name of the atom.
pos int // pos is the position number of the atom.
id int64
}
// ID satisfies the graph.Node interface.
func (n atom) ID() int64 { return n.id }
func ExampleModular_subgraphIsomorphism() {
// The modular product can be used to find subgraph isomorphisms.
// See https://doi.org/10.1016/0020-0190(76)90049-1 and for a
// theoretical perspective, https://doi.org/10.1145/990524.990529.
// We can find the common structure between two organic molecules.
// For example the purines adenine and guanine from nucleic acids.
// Make a graph for adenine.
adenine := simple.NewUndirectedGraph()
for _, bond := range []simple.Edge{
// Purine nucleus.
{F: atom{name: "N", pos: 1, id: 0}, T: atom{name: "C", pos: 2, id: 1}},
{F: atom{name: "N", pos: 1, id: 0}, T: atom{name: "C", pos: 6, id: 5}},
{F: atom{name: "C", pos: 2, id: 1}, T: atom{name: "N", pos: 3, id: 2}},
{F: atom{name: "N", pos: 3, id: 2}, T: atom{name: "C", pos: 4, id: 3}},
{F: atom{name: "C", pos: 4, id: 3}, T: atom{name: "C", pos: 5, id: 4}},
{F: atom{name: "C", pos: 4, id: 3}, T: atom{name: "N", pos: 9, id: 8}},
{F: atom{name: "C", pos: 5, id: 4}, T: atom{name: "C", pos: 6, id: 5}},
{F: atom{name: "C", pos: 5, id: 4}, T: atom{name: "N", pos: 7, id: 6}},
{F: atom{name: "N", pos: 7, id: 6}, T: atom{name: "C", pos: 8, id: 7}},
{F: atom{name: "C", pos: 8, id: 7}, T: atom{name: "N", pos: 9, id: 8}},
// Amino modification in adenine.
//
// Note that the position number of the N is non-standard.
{F: atom{name: "C", pos: 6, id: 5}, T: atom{name: "N", pos: 10, id: 9}},
} {
adenine.SetEdge(bond)
}
// Make a graph for guanine.
//
// Note that node IDs here have no intersection with
// the adenine graph to show that they are not being
// used to map between the graphs.
guanine := simple.NewUndirectedGraph()
for _, bond := range []simple.Edge{
// Purine nucleus.
{F: atom{name: "N", pos: 1, id: 10}, T: atom{name: "C", pos: 2, id: 11}},
{F: atom{name: "N", pos: 1, id: 10}, T: atom{name: "C", pos: 6, id: 15}},
{F: atom{name: "C", pos: 2, id: 11}, T: atom{name: "N", pos: 3, id: 12}},
{F: atom{name: "N", pos: 3, id: 12}, T: atom{name: "C", pos: 4, id: 13}},
{F: atom{name: "C", pos: 4, id: 13}, T: atom{name: "C", pos: 5, id: 14}},
{F: atom{name: "C", pos: 4, id: 13}, T: atom{name: "N", pos: 9, id: 18}},
{F: atom{name: "C", pos: 5, id: 14}, T: atom{name: "C", pos: 6, id: 15}},
{F: atom{name: "C", pos: 5, id: 14}, T: atom{name: "N", pos: 7, id: 16}},
{F: atom{name: "N", pos: 7, id: 16}, T: atom{name: "C", pos: 8, id: 17}},
{F: atom{name: "C", pos: 8, id: 17}, T: atom{name: "N", pos: 9, id: 18}},
// Amino and keto modifications in guanine.
//
// Note that the position number of the N and O is non-standard.
{F: atom{name: "C", pos: 2, id: 11}, T: atom{name: "N", pos: 11, id: 19}},
{F: atom{name: "C", pos: 6, id: 15}, T: atom{name: "O", pos: 10, id: 20}},
} {
guanine.SetEdge(bond)
}
// Produce the modular product of the two graphs.
p := simple.NewUndirectedGraph()
product.Modular(p, adenine, guanine)
// Find the maximal cliques in the modular product.
mc := topo.BronKerbosch(p)
// Report the largest.
sort.Sort(byLength(mc))
max := len(mc[0])
w := tabwriter.NewWriter(os.Stdout, 5, 0, 0, ' ', tabwriter.AlignRight)
fmt.Println(" Adenine Guanine")
fmt.Fprintln(w, "Atom\tPos\tAtom\tPos\t")
for _, c := range mc {
if len(c) < max {
break
}
for _, p := range c {
// Extract the mapping between the
// inputs from the product.
p := p.(product.Node)
adenine := p.A.(atom)
guanine := p.B.(atom)
fmt.Fprintf(w, "%s\t%d\t%s\t%d\t\n", adenine.name, adenine.pos, guanine.name, guanine.pos)
}
}
w.Flush()
// Unordered output:
// Adenine Guanine
// Atom Pos Atom Pos
// N 3 N 3
// N 7 N 7
// N 10 O 10
// C 6 C 6
// C 2 C 2
// C 8 C 8
// C 5 C 5
// N 9 N 9
// N 1 N 1
// C 4 C 4
}
// byLength implements the sort.Interface, sorting the slices
// descending by length.
type byLength [][]graph.Node
func (n byLength) Len() int { return len(n) }
func (n byLength) Less(i, j int) bool { return len(n[i]) > len(n[j]) }
func (n byLength) Swap(i, j int) { n[i], n[j] = n[j], n[i] }