github.com/gopherd/gonum@v0.0.4/graph/product/product_example_test.go (about)

     1  // Copyright ©2019 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package product_test
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"sort"
    11  	"text/tabwriter"
    12  
    13  	"github.com/gopherd/gonum/graph"
    14  	"github.com/gopherd/gonum/graph/product"
    15  	"github.com/gopherd/gonum/graph/simple"
    16  	"github.com/gopherd/gonum/graph/topo"
    17  )
    18  
    19  // atom is a graph.Node representing an atom in a molecule.
    20  type atom struct {
    21  	name string // name is the name of the atom.
    22  	pos  int    // pos is the position number of the atom.
    23  	id   int64
    24  }
    25  
    26  // ID satisfies the graph.Node interface.
    27  func (n atom) ID() int64 { return n.id }
    28  
    29  func ExampleModular_subgraphIsomorphism() {
    30  	// The modular product can be used to find subgraph isomorphisms.
    31  	// See https://doi.org/10.1016/0020-0190(76)90049-1 and for a
    32  	// theoretical perspective, https://doi.org/10.1145/990524.990529.
    33  
    34  	// We can find the common structure between two organic molecules.
    35  	// For example the purines adenine and guanine from nucleic acids.
    36  
    37  	// Make a graph for adenine.
    38  	adenine := simple.NewUndirectedGraph()
    39  	for _, bond := range []simple.Edge{
    40  		// Purine nucleus.
    41  		{F: atom{name: "N", pos: 1, id: 0}, T: atom{name: "C", pos: 2, id: 1}},
    42  		{F: atom{name: "N", pos: 1, id: 0}, T: atom{name: "C", pos: 6, id: 5}},
    43  		{F: atom{name: "C", pos: 2, id: 1}, T: atom{name: "N", pos: 3, id: 2}},
    44  		{F: atom{name: "N", pos: 3, id: 2}, T: atom{name: "C", pos: 4, id: 3}},
    45  		{F: atom{name: "C", pos: 4, id: 3}, T: atom{name: "C", pos: 5, id: 4}},
    46  		{F: atom{name: "C", pos: 4, id: 3}, T: atom{name: "N", pos: 9, id: 8}},
    47  		{F: atom{name: "C", pos: 5, id: 4}, T: atom{name: "C", pos: 6, id: 5}},
    48  		{F: atom{name: "C", pos: 5, id: 4}, T: atom{name: "N", pos: 7, id: 6}},
    49  		{F: atom{name: "N", pos: 7, id: 6}, T: atom{name: "C", pos: 8, id: 7}},
    50  		{F: atom{name: "C", pos: 8, id: 7}, T: atom{name: "N", pos: 9, id: 8}},
    51  
    52  		// Amino modification in adenine.
    53  		//
    54  		// Note that the position number of the N is non-standard.
    55  		{F: atom{name: "C", pos: 6, id: 5}, T: atom{name: "N", pos: 10, id: 9}},
    56  	} {
    57  		adenine.SetEdge(bond)
    58  	}
    59  
    60  	// Make a graph for guanine.
    61  	//
    62  	// Note that node IDs here have no intersection with
    63  	// the adenine graph to show that they are not being
    64  	// used to map between the graphs.
    65  	guanine := simple.NewUndirectedGraph()
    66  	for _, bond := range []simple.Edge{
    67  		// Purine nucleus.
    68  		{F: atom{name: "N", pos: 1, id: 10}, T: atom{name: "C", pos: 2, id: 11}},
    69  		{F: atom{name: "N", pos: 1, id: 10}, T: atom{name: "C", pos: 6, id: 15}},
    70  		{F: atom{name: "C", pos: 2, id: 11}, T: atom{name: "N", pos: 3, id: 12}},
    71  		{F: atom{name: "N", pos: 3, id: 12}, T: atom{name: "C", pos: 4, id: 13}},
    72  		{F: atom{name: "C", pos: 4, id: 13}, T: atom{name: "C", pos: 5, id: 14}},
    73  		{F: atom{name: "C", pos: 4, id: 13}, T: atom{name: "N", pos: 9, id: 18}},
    74  		{F: atom{name: "C", pos: 5, id: 14}, T: atom{name: "C", pos: 6, id: 15}},
    75  		{F: atom{name: "C", pos: 5, id: 14}, T: atom{name: "N", pos: 7, id: 16}},
    76  		{F: atom{name: "N", pos: 7, id: 16}, T: atom{name: "C", pos: 8, id: 17}},
    77  		{F: atom{name: "C", pos: 8, id: 17}, T: atom{name: "N", pos: 9, id: 18}},
    78  
    79  		// Amino and keto modifications in guanine.
    80  		//
    81  		// Note that the position number of the N and O is non-standard.
    82  		{F: atom{name: "C", pos: 2, id: 11}, T: atom{name: "N", pos: 11, id: 19}},
    83  		{F: atom{name: "C", pos: 6, id: 15}, T: atom{name: "O", pos: 10, id: 20}},
    84  	} {
    85  		guanine.SetEdge(bond)
    86  	}
    87  
    88  	// Produce the modular product of the two graphs.
    89  	p := simple.NewUndirectedGraph()
    90  	product.Modular(p, adenine, guanine)
    91  
    92  	// Find the maximal cliques in the modular product.
    93  	mc := topo.BronKerbosch(p)
    94  
    95  	// Report the largest.
    96  	sort.Sort(byLength(mc))
    97  	max := len(mc[0])
    98  	w := tabwriter.NewWriter(os.Stdout, 5, 0, 0, ' ', tabwriter.AlignRight)
    99  	fmt.Println("  Adenine   Guanine")
   100  	fmt.Fprintln(w, "Atom\tPos\tAtom\tPos\t")
   101  	for _, c := range mc {
   102  		if len(c) < max {
   103  			break
   104  		}
   105  		for _, p := range c {
   106  			// Extract the mapping between the
   107  			// inputs from the product.
   108  			p := p.(product.Node)
   109  			adenine := p.A.(atom)
   110  			guanine := p.B.(atom)
   111  			fmt.Fprintf(w, "%s\t%d\t%s\t%d\t\n", adenine.name, adenine.pos, guanine.name, guanine.pos)
   112  		}
   113  	}
   114  	w.Flush()
   115  
   116  	// Unordered output:
   117  	//   Adenine   Guanine
   118  	//  Atom  Pos Atom  Pos
   119  	//     N    3    N    3
   120  	//     N    7    N    7
   121  	//     N   10    O   10
   122  	//     C    6    C    6
   123  	//     C    2    C    2
   124  	//     C    8    C    8
   125  	//     C    5    C    5
   126  	//     N    9    N    9
   127  	//     N    1    N    1
   128  	//     C    4    C    4
   129  }
   130  
   131  // byLength implements the sort.Interface, sorting the slices
   132  // descending by length.
   133  type byLength [][]graph.Node
   134  
   135  func (n byLength) Len() int           { return len(n) }
   136  func (n byLength) Less(i, j int) bool { return len(n[i]) > len(n[j]) }
   137  func (n byLength) Swap(i, j int)      { n[i], n[j] = n[j], n[i] }