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  }