From fc57b6f64c9c2f4f8b4497eff7d268b9181be716 Mon Sep 17 00:00:00 2001 From: Mikalai Radchuk Date: Fri, 1 Dec 2023 15:11:00 +0000 Subject: [PATCH 1/2] Remove `DeppySolver` wrapper Signed-off-by: Mikalai Radchuk --- pkg/deppy/solver/solver.go | 72 ------------- pkg/deppy/solver/solver_test.go | 176 -------------------------------- 2 files changed, 248 deletions(-) delete mode 100644 pkg/deppy/solver/solver.go delete mode 100644 pkg/deppy/solver/solver_test.go diff --git a/pkg/deppy/solver/solver.go b/pkg/deppy/solver/solver.go deleted file mode 100644 index 785254c..0000000 --- a/pkg/deppy/solver/solver.go +++ /dev/null @@ -1,72 +0,0 @@ -package solver - -import ( - "errors" - - "github.com/operator-framework/deppy/internal/solver" - "github.com/operator-framework/deppy/pkg/deppy" -) - -// TODO: should disambiguate between solver errors due to constraints -// and other generic errors - -// Solution is returned by the Solver when the internal solver executed successfully. -// A successful execution of the solver can still end in an error when no solution can -// be found. -type Solution struct { - err error - selection map[deppy.Identifier]deppy.Variable -} - -// Error returns the resolution error in case the problem is unsat -// on successful resolution, it will return nil -func (s *Solution) Error() error { - return s.err -} - -// SelectedVariables returns the variables that were selected by the solver -// as part of the solution -func (s *Solution) SelectedVariables() map[deppy.Identifier]deppy.Variable { - return s.selection -} - -// IsSelected returns true if the variable identified by the identifier was selected -// in the solution by the resolver. It will return false otherwise. -func (s *Solution) IsSelected(identifier deppy.Identifier) bool { - _, ok := s.selection[identifier] - return ok -} - -// DeppySolver is a simple solver implementation that takes a slice of variables -// to produce a Solution (or error if no solution can be found) -type DeppySolver struct{} - -func NewDeppySolver() *DeppySolver { - return &DeppySolver{} -} - -func (d DeppySolver) Solve(vars []deppy.Variable) (*Solution, error) { - satSolver, err := solver.New() - if err != nil { - return nil, err - } - - selection, err := satSolver.Solve(vars) - if err != nil && !errors.As(err, &deppy.NotSatisfiable{}) { - return nil, err - } - - selectionMap := map[deppy.Identifier]deppy.Variable{} - for _, variable := range selection { - selectionMap[variable.Identifier()] = variable - } - - solution := &Solution{selection: selectionMap} - if err != nil { - unsatError := deppy.NotSatisfiable{} - errors.As(err, &unsatError) - solution.err = unsatError - } - - return solution, nil -} diff --git a/pkg/deppy/solver/solver_test.go b/pkg/deppy/solver/solver_test.go deleted file mode 100644 index c9568c2..0000000 --- a/pkg/deppy/solver/solver_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package solver_test - -import ( - "fmt" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" - - "github.com/operator-framework/deppy/pkg/deppy/input" - - "github.com/operator-framework/deppy/pkg/deppy/solver" - - "github.com/operator-framework/deppy/pkg/deppy/constraint" - - "github.com/operator-framework/deppy/pkg/deppy" -) - -func TestSolver(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Solver Suite") -} - -var _ = Describe("Entity", func() { - It("should select a mandatory entity", func() { - variables := []deppy.Variable{ - input.NewSimpleVariable("1", constraint.Mandatory()), - input.NewSimpleVariable("2"), - } - - so := solver.NewDeppySolver() - solution, err := so.Solve(variables) - Expect(err).ToNot(HaveOccurred()) - Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ - deppy.Identifier("1"): Equal(input.NewSimpleVariable("1", constraint.Mandatory())), - })) - }) - - It("should select two mandatory entities", func() { - variables := []deppy.Variable{ - input.NewSimpleVariable("1", constraint.Mandatory()), - input.NewSimpleVariable("2", constraint.Mandatory()), - } - - so := solver.NewDeppySolver() - solution, err := so.Solve(variables) - Expect(err).ToNot(HaveOccurred()) - Expect(solution.Error()).ToNot(HaveOccurred()) - Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ - deppy.Identifier("1"): Equal(input.NewSimpleVariable("1", constraint.Mandatory())), - deppy.Identifier("2"): Equal(input.NewSimpleVariable("2", constraint.Mandatory())), - })) - }) - - It("should select a mandatory entity and its dependency", func() { - variables := []deppy.Variable{ - input.NewSimpleVariable("1", constraint.Mandatory(), constraint.Dependency("2")), - input.NewSimpleVariable("2"), - input.NewSimpleVariable("3"), - } - - so := solver.NewDeppySolver() - solution, err := so.Solve(variables) - Expect(err).ToNot(HaveOccurred()) - Expect(solution.Error()).ToNot(HaveOccurred()) - Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ - deppy.Identifier("1"): Equal(input.NewSimpleVariable("1", constraint.Mandatory(), constraint.Dependency("2"))), - deppy.Identifier("2"): Equal(input.NewSimpleVariable("2")), - })) - }) - - It("should return untyped nil error from solution.Error() when there is a solution", func() { - variables := []deppy.Variable{ - input.NewSimpleVariable("1", constraint.Mandatory()), - } - so := solver.NewDeppySolver() - solution, err := so.Solve(variables) - Expect(err).ToNot(HaveOccurred()) - Expect(solution).ToNot(BeNil()) - - // Using this style for the assertion to ensure that gomega - // doesn't equate nil errors of different types. - if err := solution.Error(); err != nil { - Fail(fmt.Sprintf("expected solution.Error() to be untyped nil, got %#v", solution.Error())) - } - }) - - It("should place resolution errors in the solution", func() { - variables := []deppy.Variable{ - input.NewSimpleVariable("1", constraint.Mandatory(), constraint.Dependency("2")), - input.NewSimpleVariable("2", constraint.Prohibited()), - input.NewSimpleVariable("3"), - } - - so := solver.NewDeppySolver() - solution, err := so.Solve(variables) - Expect(err).ToNot(HaveOccurred()) - Expect(solution.Error()).To(HaveOccurred()) - }) - - It("should select a mandatory entity and its dependency and ignore a non-mandatory prohibited variable", func() { - variables := []deppy.Variable{ - input.NewSimpleVariable("1", constraint.Mandatory(), constraint.Dependency("2")), - input.NewSimpleVariable("2"), - input.NewSimpleVariable("3", constraint.Prohibited()), - } - - so := solver.NewDeppySolver() - solution, err := so.Solve(variables) - Expect(err).ToNot(HaveOccurred()) - Expect(solution.Error()).ToNot(HaveOccurred()) - Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ - deppy.Identifier("1"): Equal(input.NewSimpleVariable("1", constraint.Mandatory(), constraint.Dependency("2"))), - deppy.Identifier("2"): Equal(input.NewSimpleVariable("2")), - })) - }) - - It("should not select 'or' paths that are prohibited", func() { - variables := []deppy.Variable{ - input.NewSimpleVariable("1", constraint.Or("2", false, false), constraint.Dependency("3")), - input.NewSimpleVariable("2", constraint.Dependency("4")), - input.NewSimpleVariable("3", constraint.Prohibited()), - input.NewSimpleVariable("4"), - } - - so := solver.NewDeppySolver() - solution, err := so.Solve(variables) - Expect(err).ToNot(HaveOccurred()) - Expect(solution.Error()).ToNot(HaveOccurred()) - Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ - deppy.Identifier("2"): Equal(input.NewSimpleVariable("2", constraint.Dependency("4"))), - deppy.Identifier("4"): Equal(input.NewSimpleVariable("4")), - })) - }) - - It("should respect atMost constraint", func() { - variables := []deppy.Variable{ - input.NewSimpleVariable("1", constraint.Or("2", false, false), constraint.Dependency("3"), constraint.Dependency("4")), - input.NewSimpleVariable("2", constraint.Dependency("3")), - input.NewSimpleVariable("3", constraint.AtMost(1, "3", "4")), - input.NewSimpleVariable("4"), - } - - so := solver.NewDeppySolver() - solution, err := so.Solve(variables) - Expect(err).ToNot(HaveOccurred()) - Expect(solution.Error()).ToNot(HaveOccurred()) - Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ - deppy.Identifier("2"): Equal(input.NewSimpleVariable("2", constraint.Dependency("3"))), - deppy.Identifier("3"): Equal(input.NewSimpleVariable("3", constraint.AtMost(1, "3", "4"))), - })) - }) - - It("should respect dependency conflicts", func() { - variables := []deppy.Variable{ - input.NewSimpleVariable("1", constraint.Or("2", false, false), constraint.Dependency("3"), constraint.Dependency("4")), - input.NewSimpleVariable("2", constraint.Dependency("4"), constraint.Dependency("5")), - input.NewSimpleVariable("3", constraint.Conflict("6")), - input.NewSimpleVariable("4", constraint.Dependency("6")), - input.NewSimpleVariable("5"), - input.NewSimpleVariable("6"), - } - - so := solver.NewDeppySolver() - solution, err := so.Solve(variables) - Expect(err).ToNot(HaveOccurred()) - Expect(solution.Error()).ToNot(HaveOccurred()) - Expect(solution.SelectedVariables()).To(MatchAllKeys(Keys{ - deppy.Identifier("2"): Equal(input.NewSimpleVariable("2", constraint.Dependency("4"), constraint.Dependency("5"))), - deppy.Identifier("4"): Equal(input.NewSimpleVariable("4", constraint.Dependency("6"))), - deppy.Identifier("5"): Equal(input.NewSimpleVariable("5")), - deppy.Identifier("6"): Equal(input.NewSimpleVariable("6")), - })) - }) -}) From eae1c3229434d7737132a2bf0755c383278e7f5e Mon Sep 17 00:00:00 2001 From: Mikalai Radchuk Date: Fri, 1 Dec 2023 15:12:01 +0000 Subject: [PATCH 2/2] Export internal solver Signed-off-by: Mikalai Radchuk --- cmd/dimacs/cmd.go | 16 +++++++++++++--- cmd/sudoku/cmd.go | 9 ++++++--- {internal => pkg/deppy}/solver/bench_test.go | 2 +- {internal => pkg/deppy}/solver/constraints.go | 0 .../deppy}/solver/constraints_test.go | 0 {internal => pkg/deppy}/solver/doc.go | 0 {internal => pkg/deppy}/solver/lit_mapping.go | 0 {internal => pkg/deppy}/solver/search.go | 0 {internal => pkg/deppy}/solver/search_test.go | 0 {internal => pkg/deppy}/solver/solve.go | 0 {internal => pkg/deppy}/solver/solve_test.go | 0 {internal => pkg/deppy}/solver/tracer.go | 0 {internal => pkg/deppy}/solver/variable.go | 0 {internal => pkg/deppy}/solver/zz_search_test.go | 0 14 files changed, 20 insertions(+), 7 deletions(-) rename {internal => pkg/deppy}/solver/bench_test.go (89%) rename {internal => pkg/deppy}/solver/constraints.go (100%) rename {internal => pkg/deppy}/solver/constraints_test.go (100%) rename {internal => pkg/deppy}/solver/doc.go (100%) rename {internal => pkg/deppy}/solver/lit_mapping.go (100%) rename {internal => pkg/deppy}/solver/search.go (100%) rename {internal => pkg/deppy}/solver/search_test.go (100%) rename {internal => pkg/deppy}/solver/solve.go (100%) rename {internal => pkg/deppy}/solver/solve_test.go (100%) rename {internal => pkg/deppy}/solver/tracer.go (100%) rename {internal => pkg/deppy}/solver/variable.go (100%) rename {internal => pkg/deppy}/solver/zz_search_test.go (100%) diff --git a/cmd/dimacs/cmd.go b/cmd/dimacs/cmd.go index 25fcc88..fb7d1be 100644 --- a/cmd/dimacs/cmd.go +++ b/cmd/dimacs/cmd.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" + "github.com/operator-framework/deppy/pkg/deppy" "github.com/operator-framework/deppy/pkg/deppy/solver" ) @@ -52,20 +53,29 @@ func solve(path string) error { } // build solver - so := solver.NewDeppySolver() + so, err := solver.New() + if err != nil { + return err + } // get solution vars, err := GenerateVariables(dimacs) if err != nil { return fmt.Errorf("error generating variables: %s", err) } - solution, err := so.Solve(vars) + selection, err := so.Solve(vars) if err != nil { fmt.Printf("no solution found: %s\n", err) } else { + selected := map[deppy.Identifier]struct{}{} + for _, variable := range selection { + selected[variable.Identifier()] = struct{}{} + } + fmt.Println("solution found:") for _, variable := range vars { - fmt.Printf("%s = %t\n", variable.Identifier(), solution.IsSelected(variable.Identifier())) + _, ok := selected[variable.Identifier()] + fmt.Printf("%s = %t\n", variable.Identifier(), ok) } } diff --git a/cmd/sudoku/cmd.go b/cmd/sudoku/cmd.go index 5a69a2b..04e920a 100644 --- a/cmd/sudoku/cmd.go +++ b/cmd/sudoku/cmd.go @@ -22,19 +22,22 @@ func NewSudokuCommand() *cobra.Command { func solve() error { // build solver - so := solver.NewDeppySolver() + so, err := solver.New() + if err != nil { + return err + } // get solution vars, err := GenerateVariables() if err != nil { return err } - solution, err := so.Solve(vars) + selection, err := so.Solve(vars) if err != nil { fmt.Println("no solution found") } else { selected := map[deppy.Identifier]struct{}{} - for _, variable := range solution.SelectedVariables() { + for _, variable := range selection { selected[variable.Identifier()] = struct{}{} } for row := 0; row < 9; row++ { diff --git a/internal/solver/bench_test.go b/pkg/deppy/solver/bench_test.go similarity index 89% rename from internal/solver/bench_test.go rename to pkg/deppy/solver/bench_test.go index ebce225..11032f7 100644 --- a/internal/solver/bench_test.go +++ b/pkg/deppy/solver/bench_test.go @@ -20,7 +20,7 @@ var BenchmarkInput = func() []deppy.Variable { nConflict = 3 ) - random := rand.New(rand.NewSource(seed)) + random := rand.New(rand.NewSource(seed)) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive. id := func(i int) deppy.Identifier { return deppy.Identifier(strconv.Itoa(i)) diff --git a/internal/solver/constraints.go b/pkg/deppy/solver/constraints.go similarity index 100% rename from internal/solver/constraints.go rename to pkg/deppy/solver/constraints.go diff --git a/internal/solver/constraints_test.go b/pkg/deppy/solver/constraints_test.go similarity index 100% rename from internal/solver/constraints_test.go rename to pkg/deppy/solver/constraints_test.go diff --git a/internal/solver/doc.go b/pkg/deppy/solver/doc.go similarity index 100% rename from internal/solver/doc.go rename to pkg/deppy/solver/doc.go diff --git a/internal/solver/lit_mapping.go b/pkg/deppy/solver/lit_mapping.go similarity index 100% rename from internal/solver/lit_mapping.go rename to pkg/deppy/solver/lit_mapping.go diff --git a/internal/solver/search.go b/pkg/deppy/solver/search.go similarity index 100% rename from internal/solver/search.go rename to pkg/deppy/solver/search.go diff --git a/internal/solver/search_test.go b/pkg/deppy/solver/search_test.go similarity index 100% rename from internal/solver/search_test.go rename to pkg/deppy/solver/search_test.go diff --git a/internal/solver/solve.go b/pkg/deppy/solver/solve.go similarity index 100% rename from internal/solver/solve.go rename to pkg/deppy/solver/solve.go diff --git a/internal/solver/solve_test.go b/pkg/deppy/solver/solve_test.go similarity index 100% rename from internal/solver/solve_test.go rename to pkg/deppy/solver/solve_test.go diff --git a/internal/solver/tracer.go b/pkg/deppy/solver/tracer.go similarity index 100% rename from internal/solver/tracer.go rename to pkg/deppy/solver/tracer.go diff --git a/internal/solver/variable.go b/pkg/deppy/solver/variable.go similarity index 100% rename from internal/solver/variable.go rename to pkg/deppy/solver/variable.go diff --git a/internal/solver/zz_search_test.go b/pkg/deppy/solver/zz_search_test.go similarity index 100% rename from internal/solver/zz_search_test.go rename to pkg/deppy/solver/zz_search_test.go