// Copyright Β©2017 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 topo_test import ( "bufio" "fmt" "io" "log" "slices" "strings" "gonum.org/v1/gonum/graph/simple" "gonum.org/v1/gonum/graph/topo" ) var systems = []string{ // Unsatisfiable system. `π‘₯_a ∨ Β¬π‘₯_b Β¬π‘₯_b ∨ π‘₯_f π‘₯_h ∨ π‘₯_i π‘₯_a ∨ π‘₯_b π‘₯_k ∨ π‘₯_c Β¬π‘₯_f ∨ π‘₯_h π‘₯_c ∨ π‘₯_g π‘₯_f ∨ π‘₯_g π‘₯_h ∨ Β¬π‘₯_l Β¬π‘₯_h ∨ π‘₯_i π‘₯_i ∨ π‘₯_b Β¬π‘₯_i ∨ Β¬π‘₯_h π‘₯_i ∨ Β¬π‘₯_c π‘₯_l ∨ π‘₯_d Β¬π‘₯_j ∨ Β¬π‘₯_i Β¬π‘₯_a ∨ Β¬π‘₯_j Β¬π‘₯_a ∨ π‘₯_b Β¬π‘₯_d ∨ π‘₯_e Β¬π‘₯_k ∨ π‘₯_h π‘₯_l ∨ Β¬π‘₯_d π‘₯_l ∨ π‘₯_d π‘₯_l ∨ Β¬π‘₯_f π‘₯_b ∨ π‘₯_d π‘₯_b ∨ Β¬π‘₯_g π‘₯_d ∨ Β¬π‘₯_l Β¬π‘₯_l ∨ Β¬π‘₯_k `, // Satisfiable system. `π‘₯_a ∨ Β¬π‘₯_b Β¬π‘₯_b ∨ π‘₯_f π‘₯_h ∨ π‘₯_i π‘₯_a ∨ π‘₯_b π‘₯_k ∨ π‘₯_c Β¬π‘₯_f ∨ π‘₯_h π‘₯_c ∨ π‘₯_g π‘₯_f ∨ π‘₯_g π‘₯_h ∨ Β¬π‘₯_l Β¬π‘₯_h ∨ π‘₯_i π‘₯_i ∨ π‘₯_b Β¬π‘₯_i ∨ π‘₯_e π‘₯_i ∨ Β¬π‘₯_c Β¬π‘₯_g ∨ Β¬π‘₯_a π‘₯_l ∨ π‘₯_f Β¬π‘₯_j ∨ Β¬π‘₯_i Β¬π‘₯_a ∨ Β¬π‘₯_j Β¬π‘₯_a ∨ π‘₯_b Β¬π‘₯_d ∨ π‘₯_e π‘₯_k ∨ Β¬π‘₯_a π‘₯_k ∨ π‘₯_h π‘₯_l ∨ Β¬π‘₯_d π‘₯_l ∨ π‘₯_e π‘₯_l ∨ Β¬π‘₯_f π‘₯_b ∨ π‘₯_d π‘₯_b ∨ Β¬π‘₯_g π‘₯_d ∨ Β¬π‘₯_l π‘₯_l ∨ π‘₯_e `, `fun ∨ Β¬fun fun ∨ Β¬Gonum Gonum ∨ Gonum `, } // twoSat returns whether the system described in the data read from r is // satisfiable and a set of states that satisfies the system. // The syntax used by twoSat is "π‘₯ ∨ 𝑦" where π‘₯ and 𝑦 may be negated by // leading "Β¬" characters. twoSat uses the implication graph approach to // system analysis. func twoSat(r io.Reader) (state map[string]bool, ok bool) { g := simple.NewDirectedGraph() sc := bufio.NewScanner(r) nodes := make(map[string]node) for count := 1; sc.Scan(); count++ { line := sc.Text() fields := strings.Split(line, "∨") if len(fields) != 2 { log.Fatalf("failed to parse on line %d %q: invalid syntax", count, line) } var variables [2]node for i, f := range fields { f = strings.TrimSpace(f) var negate bool for strings.Index(f, "Β¬") == 0 { f = strings.TrimPrefix(f, "Β¬") negate = !negate } n, ok := nodes[f] if !ok { n = node{ id: int64(len(nodes) + 1), // id must not be zero. name: f, } nodes[f] = n } if negate { n = n.negated() } variables[i] = n } // Check for tautology. if variables[0].negated().ID() == variables[1].ID() { for _, v := range variables { if g.Node(v.ID()) == nil { g.AddNode(v) } } continue } // Add implications to the graph. g.SetEdge(simple.Edge{F: variables[0].negated(), T: variables[1]}) g.SetEdge(simple.Edge{F: variables[1].negated(), T: variables[0]}) } // Find implication inconsistencies. sccs := topo.TarjanSCC(g) for _, c := range sccs { set := make(map[int64]struct{}) for _, n := range c { id := n.ID() if _, ok := set[-id]; ok { return nil, false } set[id] = struct{}{} } } // Assign states. state = make(map[string]bool) unknown: for _, c := range sccs { for _, n := range c { if _, known := state[n.(node).name]; known { continue unknown } } for _, n := range c { n := n.(node) state[n.name] = n.id > 0 } } return state, true } type node struct { id int64 name string } func (n node) ID() int64 { return n.id } func (n node) negated() node { return node{-n.id, n.name} } func ExampleTarjanSCC_twoSAT() { for i, s := range systems { state, ok := twoSat(strings.NewReader(s)) if !ok { fmt.Printf("system %d is not satisfiable\n", i) continue } var ps []string for v, t := range state { ps = append(ps, fmt.Sprintf("%s:%t", v, t)) } slices.Sort(ps) fmt.Printf("system %d is satisfiable: %s\n", i, strings.Join(ps, " ")) } // Output: // system 0 is not satisfiable // system 1 is satisfiable: π‘₯_a:true π‘₯_b:true π‘₯_c:true π‘₯_d:true π‘₯_e:true π‘₯_f:true π‘₯_g:false π‘₯_h:true π‘₯_i:true π‘₯_j:false π‘₯_k:true π‘₯_l:true // system 2 is satisfiable: Gonum:true fun:true }