github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/solver/solve.go (about) 1 package solver 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strings" 8 9 "github.com/go-air/gini" 10 "github.com/go-air/gini/inter" 11 "github.com/go-air/gini/z" 12 ) 13 14 var ErrIncomplete = errors.New("cancelled before a solution could be found") 15 16 // NotSatisfiable is an error composed of a minimal set of applied 17 // constraints that is sufficient to make a solution impossible. 18 type NotSatisfiable []AppliedConstraint 19 20 func (e NotSatisfiable) Error() string { 21 const msg = "constraints not satisfiable" 22 if len(e) == 0 { 23 return msg 24 } 25 s := make([]string, len(e)) 26 for i, a := range e { 27 s[i] = a.String() 28 } 29 return fmt.Sprintf("%s: %s", msg, strings.Join(s, ", ")) 30 } 31 32 type Solver interface { 33 Solve(context.Context) ([]Variable, error) 34 } 35 36 type solver struct { 37 g inter.S 38 litMap *litMapping 39 tracer Tracer 40 buffer []z.Lit 41 } 42 43 const ( 44 satisfiable = 1 45 unsatisfiable = -1 46 unknown = 0 47 ) 48 49 // Solve takes a slice containing all Variables and returns a slice 50 // containing only those Variables that were selected for 51 // installation. If no solution is possible, or if the provided 52 // Context times out or is cancelled, an error is returned. 53 func (s *solver) Solve(ctx context.Context) (result []Variable, err error) { 54 defer func() { 55 // This likely indicates a bug, so discard whatever 56 // return values were produced. 57 if derr := s.litMap.Error(); derr != nil { 58 result = nil 59 err = derr 60 } 61 }() 62 63 // teach all constraints to the solver 64 s.litMap.AddConstraints(s.g) 65 66 // collect literals of all mandatory variables to assume as a baseline 67 var assumptions []z.Lit 68 for _, anchor := range s.litMap.AnchorIdentifiers() { 69 assumptions = append(assumptions, s.litMap.LitOf(anchor)) 70 } 71 72 // assume that all constraints hold 73 s.litMap.AssumeConstraints(s.g) 74 s.g.Assume(assumptions...) 75 76 var aset map[z.Lit]struct{} 77 // push a new test scope with the baseline assumptions, to prevent them from being cleared during search 78 outcome, _ := s.g.Test(nil) 79 if outcome != satisfiable && outcome != unsatisfiable { 80 // searcher for solutions in input order, so that preferences 81 // can be taken into acount (i.e. prefer one catalog to another) 82 outcome, assumptions, aset = (&search{s: s.g, lits: s.litMap, tracer: s.tracer}).Do(context.Background(), assumptions) 83 } 84 switch outcome { 85 case satisfiable: 86 s.buffer = s.litMap.Lits(s.buffer) 87 var extras, excluded []z.Lit 88 for _, m := range s.buffer { 89 if _, ok := aset[m]; ok { 90 continue 91 } 92 if !s.g.Value(m) { 93 excluded = append(excluded, m.Not()) 94 continue 95 } 96 extras = append(extras, m) 97 } 98 s.g.Untest() 99 cs := s.litMap.CardinalityConstrainer(s.g, extras) 100 s.g.Assume(assumptions...) 101 s.g.Assume(excluded...) 102 s.litMap.AssumeConstraints(s.g) 103 _, s.buffer = s.g.Test(s.buffer) 104 for w := 0; w <= cs.N(); w++ { 105 s.g.Assume(cs.Leq(w)) 106 if s.g.Solve() == satisfiable { 107 return s.litMap.Variables(s.g), nil 108 } 109 } 110 // Something is wrong if we can't find a model anymore 111 // after optimizing for cardinality. 112 return nil, fmt.Errorf("unexpected internal error") 113 case unsatisfiable: 114 return nil, NotSatisfiable(s.litMap.Conflicts(s.g)) 115 } 116 117 return nil, ErrIncomplete 118 } 119 120 func New(options ...Option) (Solver, error) { 121 s := solver{g: gini.New()} 122 for _, option := range append(options, defaults...) { 123 if err := option(&s); err != nil { 124 return nil, err 125 } 126 } 127 return &s, nil 128 } 129 130 type Option func(s *solver) error 131 132 func WithInput(input []Variable) Option { 133 return func(s *solver) error { 134 var err error 135 s.litMap, err = newLitMapping(input) 136 return err 137 } 138 } 139 140 func WithTracer(t Tracer) Option { 141 return func(s *solver) error { 142 s.tracer = t 143 return nil 144 } 145 } 146 147 var defaults = []Option{ 148 func(s *solver) error { 149 if s.litMap == nil { 150 var err error 151 s.litMap, err = newLitMapping(nil) 152 return err 153 } 154 return nil 155 }, 156 func(s *solver) error { 157 if s.tracer == nil { 158 s.tracer = DefaultTracer{} 159 } 160 return nil 161 }, 162 }