From f068bfdb9bdd654577a1b5da711f920877755354 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Mon, 10 Feb 2020 09:41:09 +0100 Subject: [PATCH 01/30] Add PackageResolver to add heuristics on unsat solutions --- pkg/solver/resolver.go | 35 +++++++++++++++++++++++++++++++++++ pkg/solver/solver.go | 22 +++++++++++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 pkg/solver/resolver.go diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go new file mode 100644 index 00000000..f528c363 --- /dev/null +++ b/pkg/solver/resolver.go @@ -0,0 +1,35 @@ +// Copyright © 2020 Ettore Di Giacinto +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package solver + +import ( + "github.com/crillab/gophersat/bf" + "github.com/pkg/errors" +) + +//. "github.com/mudler/luet/pkg/logger" + +// PackageResolver assists PackageSolver on unsat cases +type PackageResolver interface { + Solve(bf.Formula, PackageSolver) (map[string]bool, error) +} + +type DummyPackageResolver struct { +} + +func (*DummyPackageResolver) Solve(bf.Formula, PackageSolver) (map[string]bool, error) { + return map[string]bool{}, errors.New("Could not satisfy the constraints. Try again by removing deps ") +} diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index 03ebec1e..583d3758 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -33,6 +33,8 @@ type PackageSolver interface { ConflictsWith(p pkg.Package, ls []pkg.Package) (bool, error) World() []pkg.Package Upgrade() ([]pkg.Package, PackagesAssertions, error) + + SetResolver(PackageResolver) } // Solver is the default solver for luet @@ -41,20 +43,27 @@ type Solver struct { SolverDatabase pkg.PackageDatabase Wanted []pkg.Package InstalledDatabase pkg.PackageDatabase + + Resolver PackageResolver } // NewSolver accepts as argument two lists of packages, the first is the initial set, // the second represent all the known packages. func NewSolver(installed pkg.PackageDatabase, definitiondb pkg.PackageDatabase, solverdb pkg.PackageDatabase) PackageSolver { - return &Solver{InstalledDatabase: installed, DefinitionDatabase: definitiondb, SolverDatabase: solverdb} + return &Solver{InstalledDatabase: installed, DefinitionDatabase: definitiondb, SolverDatabase: solverdb, Resolver: &DummyPackageResolver{}} } -// SetWorld is a setter for the list of all known packages to the solver +// SetDefinitionDatabase is a setter for the definition Database func (s *Solver) SetDefinitionDatabase(db pkg.PackageDatabase) { s.DefinitionDatabase = db } +// SetResolver is a setter for the unsat resolver backend +func (s *Solver) SetResolver(r PackageResolver) { + s.Resolver = r +} + func (s *Solver) World() []pkg.Package { return s.DefinitionDatabase.World() } @@ -361,13 +370,20 @@ func (s *Solver) solve(f bf.Formula) (map[string]bool, bf.Formula, error) { // Solve builds the formula given the current state and returns package assertions func (s *Solver) Solve() (PackagesAssertions, error) { + var model map[string]bool + var err error + f, err := s.BuildFormula() if err != nil { return nil, err } - model, _, err := s.solve(f) + model, _, err = s.solve(f) + if err != nil && s.Resolver != nil { + model, err = s.Resolver.Solve(f, s) + } + if err != nil { return nil, err } From 33b442a832f8dd9c9a80a7b8cffebcfe2bdf158d Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Mon, 10 Feb 2020 17:14:46 +0100 Subject: [PATCH 02/30] Add accessor to decode from package String() --- pkg/package/package.go | 24 ++++++++++++++++++------ pkg/package/package_test.go | 9 +++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/pkg/package/package.go b/pkg/package/package.go index 7fd9d56b..dfde4847 100644 --- a/pkg/package/package.go +++ b/pkg/package/package.go @@ -85,6 +85,8 @@ type Package interface { IsSelector() bool VersionMatchSelector(string) (bool, error) SelectorMatchVersion(string) (bool, error) + + String() string } type Tree interface { @@ -128,11 +130,11 @@ func (t *DefaultPackage) JSON() ([]byte, error) { // DefaultPackage represent a standard package definition type DefaultPackage struct { - ID int `storm:"id,increment"` // primary key with auto increment - Name string `json:"name"` // Affects YAML field names too. - Version string `json:"version"` // Affects YAML field names too. - Category string `json:"category"` // Affects YAML field names too. - UseFlags []string `json:"use_flags"` // Affects YAML field names too. + ID int `storm:"id,increment" json:"id"` // primary key with auto increment + Name string `json:"name"` // Affects YAML field names too. + Version string `json:"version"` // Affects YAML field names too. + Category string `json:"category"` // Affects YAML field names too. + UseFlags []string `json:"use_flags"` // Affects YAML field names too. State State PackageRequires []*DefaultPackage `json:"requires"` // Affects YAML field names too. PackageConflicts []*DefaultPackage `json:"conflicts"` // Affects YAML field names too. @@ -160,7 +162,7 @@ func NewPackage(name, version string, requires []*DefaultPackage, conflicts []*D func (p *DefaultPackage) String() string { b, err := p.JSON() if err != nil { - return fmt.Sprintf("{ id: \"%d\", name: \"%s\" }", p.ID, p.Name) + return fmt.Sprintf("{ id: \"%d\", name: \"%s\", version: \"%s\", category: \"%s\" }", p.ID, p.Name, p.Version, p.Category) } return fmt.Sprintf("%s", string(b)) } @@ -171,6 +173,16 @@ func (p *DefaultPackage) GetFingerPrint() string { return fmt.Sprintf("%s-%s-%s", p.Name, p.Category, p.Version) } +func FromString(s string) Package { + var unescaped DefaultPackage + + err := json.Unmarshal([]byte(s), &unescaped) + if err != nil { + return &unescaped + } + return &unescaped +} + func (p *DefaultPackage) GetPackageName() string { return fmt.Sprintf("%s-%s", p.Name, p.Category) } diff --git a/pkg/package/package_test.go b/pkg/package/package_test.go index b23202e7..b94b72b1 100644 --- a/pkg/package/package_test.go +++ b/pkg/package/package_test.go @@ -23,6 +23,15 @@ import ( var _ = Describe("Package", func() { + Context("Encoding/Decoding", func() { + a := &DefaultPackage{Name: "test", Version: "1", Category: "t"} + It("Encodes and decodes correctly", func() { + Expect(a.String()).ToNot(Equal("")) + p := FromString(a.String()) + Expect(p).To(Equal(a)) + }) + }) + Context("Simple package", func() { a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{}) a1 := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{}) From 6d450d3af0921932f1d922159a624bec6c9759ab Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Mon, 10 Feb 2020 17:16:35 +0100 Subject: [PATCH 03/30] Add simple QLearning solver support Still experimental, it covers very small subset of action domains (just removal from target). Added a pending test that currently fails --- go.mod | 6 +- go.sum | 26 +--- pkg/solver/resolver.go | 239 +++++++++++++++++++++++++++++++++++- pkg/solver/resolver_test.go | 150 ++++++++++++++++++++++ pkg/solver/solver.go | 5 +- 5 files changed, 396 insertions(+), 30 deletions(-) create mode 100644 pkg/solver/resolver_test.go diff --git a/go.mod b/go.mod index 03fe9ebc..eb00521f 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,14 @@ go 1.12 require ( github.com/DataDog/zstd v1.4.4 // indirect - github.com/MottainaiCI/simplestreams-builder v0.0.0-20190710131531-efb382161f56 // indirect github.com/Sabayon/pkgs-checker v0.4.2-0.20200101193228-1d500105afb7 github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154 github.com/briandowns/spinner v1.7.0 github.com/cavaliercoder/grab v2.0.0+incompatible - github.com/creack/pty v1.1.9 // indirect github.com/crillab/gophersat v1.1.7 github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23 + github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd github.com/ghodss/yaml v1.0.0 - github.com/go-yaml/yaml v2.1.0+incompatible // indirect github.com/hashicorp/go-version v1.2.0 github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3 github.com/klauspost/pgzip v1.2.1 @@ -29,7 +27,6 @@ require ( github.com/pelletier/go-toml v1.6.0 // indirect github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f github.com/pkg/errors v0.8.1 - github.com/rogpeppe/go-internal v1.5.1 // indirect github.com/spf13/afero v1.2.2 // indirect github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 // indirect @@ -46,6 +43,5 @@ require ( golang.org/x/tools v0.0.0-20200102200121-6de373a2766c // indirect golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect gopkg.in/yaml.v2 v2.2.7 - mvdan.cc/sh v2.6.4+incompatible // indirect mvdan.cc/sh/v3 v3.0.0-beta1 ) diff --git a/go.sum b/go.sum index e29b47fd..0c447d04 100644 --- a/go.sum +++ b/go.sum @@ -9,15 +9,9 @@ github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6 github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/MottainaiCI/simplestreams-builder v0.0.0-20190710131531-efb382161f56 h1:XCZM9J5KqLsr5NqtrZuXiD3X5fe5IfgU7IIUZzpeFBk= -github.com/MottainaiCI/simplestreams-builder v0.0.0-20190710131531-efb382161f56/go.mod h1:+Gbv6dg6TPHWq4oDjZY1vn978PLCEZ2hOu8kvn+S7t4= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/Sabayon/pkgs-checker v0.4.1 h1:NImZhA5Z9souyr9Ff3nDzP0Bs9SGuDLxRzduVqci3dQ= -github.com/Sabayon/pkgs-checker v0.4.1/go.mod h1:GFGM6ZzSE5owdGgjLnulj0+Vt9UTd5LFGmB2AOVPYrE= -github.com/Sabayon/pkgs-checker v0.4.2-0.20191225230938-5ebdcd474e55 h1:rblyWFJ61sx69WwRsHK/BHcBlFm5X38sQmJnzVhyUoE= -github.com/Sabayon/pkgs-checker v0.4.2-0.20191225230938-5ebdcd474e55/go.mod h1:GFGM6ZzSE5owdGgjLnulj0+Vt9UTd5LFGmB2AOVPYrE= github.com/Sabayon/pkgs-checker v0.4.2-0.20200101193228-1d500105afb7 h1:Vf80sSLu1ZWjjMmUKhw0FqM43lEOvT8O5B22NaHB6AQ= github.com/Sabayon/pkgs-checker v0.4.2-0.20200101193228-1d500105afb7/go.mod h1:GFGM6ZzSE5owdGgjLnulj0+Vt9UTd5LFGmB2AOVPYrE= github.com/Sereal/Sereal v0.0.0-20181211220259-509a78ddbda3 h1:Xu7z47ZiE/J+sKXHZMGxEor/oY2q6dq51fkO0JqdSwY= @@ -59,7 +53,6 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/crillab/gophersat v1.1.7 h1:f2Phe0W9jGyN1OefygKdcTdNM99q/goSjbWrFRjZGWc= github.com/crillab/gophersat v1.1.7/go.mod h1:S91tHga1PCZzYhCkStwZAhvp1rCc+zqtSi55I+vDWGc= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= @@ -82,6 +75,8 @@ github.com/docker/libnetwork v0.8.0-dev.2.0.20180608203834-19279f049241 h1:+ebE/ github.com/docker/libnetwork v0.8.0-dev.2.0.20180608203834-19279f049241/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd h1:JWEotl+g5uCCn37eVAYLF3UjBqO5HJ0ezZ5Zgnsdoqc= +github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd/go.mod h1:y0+kb0ORo7mC8lQbUzC4oa7ufu565J6SyUgWd39Z1Ic= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -94,8 +89,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= -github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= @@ -152,6 +145,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= @@ -204,7 +198,9 @@ github.com/opencontainers/runtime-spec v1.0.1 h1:wY4pOY8fBdSIvs9+IDHC55thBuEulhz github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc= github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 h1:+OLn68pqasWca0z5ryit9KGfp3sUsW4Lqg32iRMJyzs= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/mint v1.3.0 h1:Ady6MKVezQwHBkGzLFbrsywyp09Ah7rkmfjV3Bcr5uc= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= @@ -231,7 +227,6 @@ github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.5.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rootless-containers/proto v0.1.0 h1:gS1JOMEtk1YDYHCzBAf/url+olMJbac7MTrgSeP6zh4= github.com/rootless-containers/proto v0.1.0/go.mod h1:vgkUFZbQd0gcE/K/ZwtE4MYjZPu0UNHLXIQxhyqAFh8= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= @@ -266,8 +261,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4= github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= github.com/stevenle/topsort v0.0.0-20130922064739-8130c1d7596b h1:wJSBFlabo96ySlmSX0a02WAPyGxagzTo9c5sk3sHP3E= @@ -326,8 +319,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3ob3lmhYIefc+GU+DLg1Ow= -golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -336,7 +327,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -372,8 +362,6 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4 h1:Hynbrlo6LbYI3H1IqXpkVDOcX/3HiPdhVEuyj5a59RM= -golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200102141924-c96a22e43c9c h1:OYFUffxXPezb7BVTx9AaD4Vl0qtxmklBIkwCKH1YwDY= golang.org/x/sys v0.0.0-20200102141924-c96a22e43c9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -419,8 +407,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.1.0+incompatible h1:5USw7CrJBYKqjg9R7QlA6jzqZKEAtvW82aNmsxxGPxw= @@ -429,7 +415,5 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= mvdan.cc/editorconfig v0.1.1-0.20191109213504-890940e3f00e/go.mod h1:Ge4atmRUYqueGppvJ7JNrtqpqokoJEFxYbP0Z+WeKS8= -mvdan.cc/sh v2.6.4+incompatible h1:eD6tDeh0pw+/TOTI1BBEryZ02rD2nMcFsgcvde7jffM= -mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8= mvdan.cc/sh/v3 v3.0.0-beta1 h1:UqiwBEXEPzelaGxuvixaOtzc7WzKtrElePJ8HqvW7K8= mvdan.cc/sh/v3 v3.0.0-beta1/go.mod h1:rBIndNJFYPp8xSppiZcGIk6B5d1g3OEARxEaXjPxwVI= diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go index f528c363..f26f8282 100644 --- a/pkg/solver/resolver.go +++ b/pkg/solver/resolver.go @@ -16,20 +16,253 @@ package solver import ( + "fmt" + "github.com/crillab/gophersat/bf" + "github.com/ecooper/qlearning" + pkg "github.com/mudler/luet/pkg/package" "github.com/pkg/errors" ) +const ( + Solved = 1 + NoSolution = iota + Going = iota +) + //. "github.com/mudler/luet/pkg/logger" // PackageResolver assists PackageSolver on unsat cases type PackageResolver interface { - Solve(bf.Formula, PackageSolver) (map[string]bool, error) + Solve(bf.Formula, PackageSolver) (PackagesAssertions, error) } type DummyPackageResolver struct { } -func (*DummyPackageResolver) Solve(bf.Formula, PackageSolver) (map[string]bool, error) { - return map[string]bool{}, errors.New("Could not satisfy the constraints. Try again by removing deps ") +func (*DummyPackageResolver) Solve(bf.Formula, PackageSolver) (PackagesAssertions, error) { + return nil, errors.New("Could not satisfy the constraints. Try again by removing deps ") +} + +type QLearningResolver struct { + Attempts int + + ToAttempt int + Attempted map[string]bool + Correct []string + + Solver PackageSolver + Formula bf.Formula + + Targets []pkg.Package + Current []pkg.Package + + Agent *qlearning.SimpleAgent + + debug bool +} + +func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (PackagesAssertions, error) { + resolver.Solver = s + + s.SetResolver(&DummyPackageResolver{}) // Set dummy. Otherwise the attempts will run again a QLearning instance. + defer s.SetResolver(resolver) // Set back ourselves as resolver + + resolver.Formula = f + // Our agent has a learning rate of 0.7 and discount of 1.0. + resolver.Agent = qlearning.NewSimpleAgent(0.7, 1.0) // FIXME: Remove hardcoded values + resolver.ToAttempt = len(resolver.Solver.(*Solver).Wanted) - 1 // TODO: type assertions must go away + + resolver.Targets = resolver.Solver.(*Solver).Wanted + + fmt.Println("Targets", resolver.Targets) + + resolver.Attempts = 99 + resolver.Attempted = make(map[string]bool, len(resolver.Targets)) + + resolver.Correct = make([]string, len(resolver.Targets), len(resolver.Targets)) + resolver.debug = true + for resolver.IsComplete() == Going { + // Pick the next move, which is going to be a letter choice. + action := qlearning.Next(resolver.Agent, resolver) + + // Whatever that choice is, let's update our model for its + // impact. If the package chosen makes the formula sat, + // then this action will be positive. Otherwise, it will be + // negative. + resolver.Agent.Learn(action, resolver) + + // Reward doesn't change state so we can check what the + // reward would be for this action, and report how the + // env changed. + if resolver.Reward(action) > 0.0 { + resolver.Log("%s was correct", action.Action.String()) + } else { + resolver.Log("%s was incorrect", action.Action.String()) + } + } + + // If we get good result, take it + if resolver.IsComplete() == Solved { + resolver.Log("Victory!") + resolver.Log("removals needed: ", resolver.Correct) + p := []pkg.Package{} + fmt.Println("Targets", resolver.Targets) + // Strip from targets the ones that the agent removed + TARGET: + for _, pack := range resolver.Targets { + for _, w := range resolver.Correct { + if pack.String() == w { + fmt.Println("Skipping", pack.String()) + continue TARGET + } + + } + fmt.Println("Appending", pack.String()) + + p = append(p, pack) + } + fmt.Println("Installing") + for _, pack := range p { + fmt.Println(pack.String()) + } + resolver.Solver.(*Solver).Wanted = p + return resolver.Solver.Solve() + } else { + resolver.Log("Resolver couldn't find a solution!") + return nil, errors.New("QLearning resolver failed ") + } + +} + +// Returns the current state. +func (resolver *QLearningResolver) IsComplete() int { + if resolver.Attempts < 1 { + resolver.Log("Attempts finished!") + return NoSolution + } + + if resolver.ToAttempt > 0 { + resolver.Log("We must continue!") + return Going + } + + resolver.Log("we solved it!") + return Solved +} + +// Choose applies a pack attempt, returning +// true if the formula returns sat. +// +// Choose updates the resolver's state. +func (resolver *QLearningResolver) Choose(pack string) bool { + resolver.Attempted[pack] = true + + s, _ := resolver.Solver.(*Solver) + + var filtered []pkg.Package + var index int + + //Filter by fingerprint + for i, p := range s.Wanted { + if p.String() != pack { + index = i + filtered = append(filtered, p) + } + } + + resolver.Solver.(*Solver).Wanted = filtered + //resolver.Current = filtered + + _, err := resolver.Solver.Solve() + //resolver.Solver.(*Solver).Wanted = resolver.Targets + + //resolver.Solver.(*Solver).Wanted = resolver.Targets + + if err == nil { + resolver.Correct[index] = pack + resolver.ToAttempt-- + } else { + resolver.Attempts-- + return false + } + + return true +} + +// Reward returns a score for a given qlearning.StateAction. Reward is a +// member of the qlearning.Rewarder interface. If the choice will make sat the formula, a positive score is returned. +// Otherwise, a static -1000 is returned. +func (resolver *QLearningResolver) Reward(action *qlearning.StateAction) float32 { + choice := action.Action.String() + + var filtered []pkg.Package + + //Filter by fingerprint + for _, p := range resolver.Targets { + if p.String() != choice { + filtered = append(filtered, p) + } + } + + resolver.Solver.(*Solver).Wanted = filtered + //resolver.Current = filtered + _, err := resolver.Solver.Solve() + //resolver.Solver.(*Solver).Wanted = resolver.Targets + + if err == nil { + return 24.0 / float32(len(resolver.Attempted)) + + } + + return -1000 +} + +// Next creates a new slice of qlearning.Action instances. A possible +// action is created for each package that could be removed from the formula's target +func (resolver *QLearningResolver) Next() []qlearning.Action { + actions := make([]qlearning.Action, 0, len(resolver.Targets)-1) + + for _, pack := range resolver.Targets { + attempted := resolver.Attempted[pack.String()] + if !attempted { + actions = append(actions, &Choice{Package: pack.String()}) + } + } + fmt.Println("ACTIONS", actions) + fmt.Println("_______") + return actions +} + +// Log is a wrapper of fmt.Printf. If Game.debug is true, Log will print +// to stdout. +func (resolver *QLearningResolver) Log(msg string, args ...interface{}) { + if resolver.debug { + logMsg := fmt.Sprintf("(%d moves, %d remaining attempts) %s\n", len(resolver.Attempted), resolver.Attempts, msg) + fmt.Printf(logMsg, args...) + } +} + +// String returns a consistent hash for the current env state to be +// used in a qlearning.Agent. +func (resolver *QLearningResolver) String() string { + return fmt.Sprintf("%s", resolver.Correct) +} + +// Choice implements qlearning.Action for a package choice for removal from wanted targets +type Choice struct { + Package string +} + +// String returns the character for the current action. +func (choice *Choice) String() string { + return choice.Package +} + +// Apply updates the state of the solver for the package choice. +func (choice *Choice) Apply(state qlearning.State) qlearning.State { + resolver := state.(*QLearningResolver) + resolver.Choose(choice.Package) + + return resolver } diff --git a/pkg/solver/resolver_test.go b/pkg/solver/resolver_test.go new file mode 100644 index 00000000..7ca94116 --- /dev/null +++ b/pkg/solver/resolver_test.go @@ -0,0 +1,150 @@ +// Copyright © 2019 Ettore Di Giacinto +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package solver_test + +import ( + pkg "github.com/mudler/luet/pkg/package" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + . "github.com/mudler/luet/pkg/solver" +) + +var _ = Describe("Resolver", func() { + + db := pkg.NewInMemoryDatabase(false) + dbInstalled := pkg.NewInMemoryDatabase(false) + dbDefinitions := pkg.NewInMemoryDatabase(false) + s := NewSolver(dbInstalled, dbDefinitions, db) + + BeforeEach(func() { + db = pkg.NewInMemoryDatabase(false) + dbInstalled = pkg.NewInMemoryDatabase(false) + dbDefinitions = pkg.NewInMemoryDatabase(false) + s = NewSolver(dbInstalled, dbDefinitions, db) + }) + + Context("Conflict set", func() { + Context("DummyPackageResolver", func() { + It("is unsolvable - as we something we ask to install conflict with system stuff", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{C} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + solution, err := s.Install([]pkg.Package{A}) + Expect(len(solution)).To(Equal(0)) + Expect(err).To(HaveOccurred()) + }) + }) + Context("QLearningResolver", func() { + It("will find out that we can install D by ignoring A", func() { + s.SetResolver(&QLearningResolver{}) + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{C} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + solution, err := s.Install([]pkg.Package{A, D}) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(solution)).To(Equal(4)) + + Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false})) + Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: false})) + Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true})) + }) + PIt("will find out that we can install D by ignoring A", func() { + s.SetResolver(&QLearningResolver{}) + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D, E, F} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{C} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + solution, err := s.Install([]pkg.Package{A, D, E, F}) // D and F should go as they have no deps. A/E should be filtered by QLearn + Expect(err).ToNot(HaveOccurred()) + + Expect(len(solution)).To(Equal(6)) + + Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false})) + Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: false})) + Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: E, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: F, Value: true})) + + }) + }) + + Context("DummyPackageResolver", func() { + It("cannot find a solution", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{C} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + solution, err := s.Install([]pkg.Package{A, D}) + Expect(err).To(HaveOccurred()) + + Expect(len(solution)).To(Equal(0)) + }) + + }) + }) + +}) diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index 583d3758..3abf5846 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -35,6 +35,8 @@ type PackageSolver interface { Upgrade() ([]pkg.Package, PackagesAssertions, error) SetResolver(PackageResolver) + + Solve() (PackagesAssertions, error) } // Solver is the default solver for luet @@ -230,6 +232,7 @@ func (s *Solver) Upgrade() ([]pkg.Package, PackagesAssertions, error) { } s2 := NewSolver(installedcopy, s.DefinitionDatabase, pkg.NewInMemoryDatabase(false)) + s2.SetResolver(s.Resolver) // Then try to uninstall the versions in the system, and store that tree for _, p := range toUninstall { r, err := s.Uninstall(p) @@ -381,7 +384,7 @@ func (s *Solver) Solve() (PackagesAssertions, error) { model, _, err = s.solve(f) if err != nil && s.Resolver != nil { - model, err = s.Resolver.Solve(f, s) + return s.Resolver.Solve(f, s) } if err != nil { From 6f6e2bf15f305a1327ab17b6a51c9d0227acdaf3 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 09:03:46 +0100 Subject: [PATCH 04/30] Pin to gophersat version Having the same var in the and block seems to make gophersat crash. Even if might be unoptimal, we need this to tighten the conditions between packages. Switch to gophersat fork until this fix is merged upstream: https://github.com/crillab/gophersat/pull/17 --- go.mod | 2 +- go.sum | 4 ++-- pkg/package/package.go | 2 +- pkg/solver/resolver.go | 2 +- pkg/solver/solver.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index eb00521f..ad505d74 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154 github.com/briandowns/spinner v1.7.0 github.com/cavaliercoder/grab v2.0.0+incompatible - github.com/crillab/gophersat v1.1.7 github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23 github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd github.com/ghodss/yaml v1.0.0 @@ -21,6 +20,7 @@ require ( github.com/marcsauter/single v0.0.0-20181104081128-f8bf46f26ec0 github.com/mattn/go-isatty v0.0.10 // indirect github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616 + github.com/mudler/gophersat v1.1.5-0.20200211080010-645700b4b7c0 github.com/onsi/ginkgo v1.10.1 github.com/onsi/gomega v1.7.0 github.com/otiai10/copy v1.0.2 diff --git a/go.sum b/go.sum index 0c447d04..46c9eb1b 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,6 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/crillab/gophersat v1.1.7 h1:f2Phe0W9jGyN1OefygKdcTdNM99q/goSjbWrFRjZGWc= -github.com/crillab/gophersat v1.1.7/go.mod h1:S91tHga1PCZzYhCkStwZAhvp1rCc+zqtSi55I+vDWGc= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -177,6 +175,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616 h1:JwaU8XCtGoY43bo15sKp7qpJ5eaa5vFtRW/oE8yUY/4= github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616/go.mod h1:5uUdicxxQ0H591kxsvbZtgjY11OWVUuDShh08gg60sI= +github.com/mudler/gophersat v1.1.5-0.20200211080010-645700b4b7c0 h1:isVUEy1f9OHq+nvWVKfSifqC5aKktrQsnkuVUkjKKGU= +github.com/mudler/gophersat v1.1.5-0.20200211080010-645700b4b7c0/go.mod h1:nT09AIliwEfZq3cJE+u90vftWDejp1H060GuqS//aHk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/pkg/package/package.go b/pkg/package/package.go index dfde4847..2b096e42 100644 --- a/pkg/package/package.go +++ b/pkg/package/package.go @@ -25,7 +25,7 @@ import ( // . "github.com/mudler/luet/pkg/logger" - "github.com/crillab/gophersat/bf" + "github.com/mudler/gophersat/bf" version "github.com/hashicorp/go-version" "github.com/jinzhu/copier" diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go index f26f8282..170b7f44 100644 --- a/pkg/solver/resolver.go +++ b/pkg/solver/resolver.go @@ -18,7 +18,7 @@ package solver import ( "fmt" - "github.com/crillab/gophersat/bf" + "github.com/mudler/gophersat/bf" "github.com/ecooper/qlearning" pkg "github.com/mudler/luet/pkg/package" "github.com/pkg/errors" diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index 3abf5846..8b9f1ce6 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -20,7 +20,7 @@ import ( //. "github.com/mudler/luet/pkg/logger" "github.com/pkg/errors" - "github.com/crillab/gophersat/bf" + "github.com/mudler/gophersat/bf" pkg "github.com/mudler/luet/pkg/package" ) From 1b90407475407f92a04ea14e9625355b4086f0bc Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 09:21:25 +0100 Subject: [PATCH 05/30] Don't preclude action space --- pkg/solver/resolver.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go index 170b7f44..4f8598d9 100644 --- a/pkg/solver/resolver.go +++ b/pkg/solver/resolver.go @@ -224,10 +224,10 @@ func (resolver *QLearningResolver) Next() []qlearning.Action { actions := make([]qlearning.Action, 0, len(resolver.Targets)-1) for _, pack := range resolver.Targets { - attempted := resolver.Attempted[pack.String()] - if !attempted { - actions = append(actions, &Choice{Package: pack.String()}) - } + // attempted := resolver.Attempted[pack.String()] + // if !attempted { + actions = append(actions, &Choice{Package: pack.String()}) + // } } fmt.Println("ACTIONS", actions) fmt.Println("_______") From d4255b086bd95ecddabe7460f8fa7900da042af7 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 09:50:26 +0100 Subject: [PATCH 06/30] Uncomment failing test Add test to support that normally we need to pass explictly the installable ones only. --- pkg/solver/resolver_test.go | 40 +++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/pkg/solver/resolver_test.go b/pkg/solver/resolver_test.go index 7ca94116..90fb5ae3 100644 --- a/pkg/solver/resolver_test.go +++ b/pkg/solver/resolver_test.go @@ -58,6 +58,38 @@ var _ = Describe("Resolver", func() { Expect(len(solution)).To(Equal(0)) Expect(err).To(HaveOccurred()) }) + It("succeeds to install D and F if explictly requested", func() { + C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C}) + A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) + F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) + + for _, p := range []pkg.Package{A, B, C, D, E, F} { + _, err := dbDefinitions.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + for _, p := range []pkg.Package{C} { + _, err := dbInstalled.CreatePackage(p) + Expect(err).ToNot(HaveOccurred()) + } + + solution, err := s.Install([]pkg.Package{D, F}) // D and F should go as they have no deps. A/E should be filtered by QLearn + Expect(err).ToNot(HaveOccurred()) + + Expect(len(solution)).To(Equal(6)) + + Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false})) + Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: false})) + Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: E, Value: false})) + Expect(solution).To(ContainElement(PackageAssert{Package: F, Value: true})) + + }) + }) Context("QLearningResolver", func() { It("will find out that we can install D by ignoring A", func() { @@ -87,7 +119,8 @@ var _ = Describe("Resolver", func() { Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true})) }) - PIt("will find out that we can install D by ignoring A", func() { + + It("will find out that we can install D and F by ignoring E and A", func() { s.SetResolver(&QLearningResolver{}) C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C}) @@ -113,11 +146,10 @@ var _ = Describe("Resolver", func() { Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false})) Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: false})) - Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) // Was already installed Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true})) - Expect(solution).To(ContainElement(PackageAssert{Package: E, Value: true})) + Expect(solution).To(ContainElement(PackageAssert{Package: E, Value: false})) Expect(solution).To(ContainElement(PackageAssert{Package: F, Value: true})) - }) }) From ac6554c291e7e27b672a447f129be5a4edd36b8d Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 09:51:52 +0100 Subject: [PATCH 07/30] Support Add/Removal actions Also keep the list of the wanted targets as we walk it. We will let the agent to try different solutions --- pkg/solver/resolver.go | 93 ++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go index 4f8598d9..79ae94e9 100644 --- a/pkg/solver/resolver.go +++ b/pkg/solver/resolver.go @@ -17,17 +17,22 @@ package solver import ( "fmt" + "strconv" - "github.com/mudler/gophersat/bf" "github.com/ecooper/qlearning" + "github.com/mudler/gophersat/bf" pkg "github.com/mudler/luet/pkg/package" "github.com/pkg/errors" ) +type ActionType int + const ( - Solved = 1 - NoSolution = iota - Going = iota + Solved = 1 + NoSolution = iota + Going = iota + ActionRemoved = iota + ActionAdded = iota ) //. "github.com/mudler/luet/pkg/logger" @@ -49,7 +54,7 @@ type QLearningResolver struct { ToAttempt int Attempted map[string]bool - Correct []string + Correct []Choice Solver PackageSolver Formula bf.Formula @@ -80,7 +85,7 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package resolver.Attempts = 99 resolver.Attempted = make(map[string]bool, len(resolver.Targets)) - resolver.Correct = make([]string, len(resolver.Targets), len(resolver.Targets)) + resolver.Correct = make([]Choice, len(resolver.Targets), len(resolver.Targets)) resolver.debug = true for resolver.IsComplete() == Going { // Pick the next move, which is going to be a letter choice. @@ -97,6 +102,7 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package // env changed. if resolver.Reward(action) > 0.0 { resolver.Log("%s was correct", action.Action.String()) + resolver.ToAttempt = 0 // We won. As we had one sat, let's take it } else { resolver.Log("%s was incorrect", action.Action.String()) } @@ -112,7 +118,7 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package TARGET: for _, pack := range resolver.Targets { for _, w := range resolver.Correct { - if pack.String() == w { + if pack.String() == w.String() { fmt.Println("Skipping", pack.String()) continue TARGET } @@ -151,36 +157,48 @@ func (resolver *QLearningResolver) IsComplete() int { return Solved } +func (resolver *QLearningResolver) Try(c Choice) error { + pack := c.String() + resolver.Attempted[pack+strconv.Itoa(int(c.Action))] = true // increase the count + s, _ := resolver.Solver.(*Solver) + var filtered []pkg.Package + + switch c.Action { + case ActionAdded: + for _, p := range resolver.Targets { + if p.String() == pack { + resolver.Solver.(*Solver).Wanted = append(resolver.Solver.(*Solver).Wanted, p) + } + } + + case ActionRemoved: + for _, p := range s.Wanted { + if p.String() != pack { + filtered = append(filtered, p) + } + } + + resolver.Solver.(*Solver).Wanted = filtered + default: + return errors.New("Nonvalid action") + + } + + _, err := resolver.Solver.Solve() + + return err +} + // Choose applies a pack attempt, returning // true if the formula returns sat. // // Choose updates the resolver's state. -func (resolver *QLearningResolver) Choose(pack string) bool { - resolver.Attempted[pack] = true - - s, _ := resolver.Solver.(*Solver) - - var filtered []pkg.Package - var index int - - //Filter by fingerprint - for i, p := range s.Wanted { - if p.String() != pack { - index = i - filtered = append(filtered, p) - } - } - - resolver.Solver.(*Solver).Wanted = filtered - //resolver.Current = filtered - - _, err := resolver.Solver.Solve() - //resolver.Solver.(*Solver).Wanted = resolver.Targets - - //resolver.Solver.(*Solver).Wanted = resolver.Targets +func (resolver *QLearningResolver) Choose(c Choice) bool { + err := resolver.Try(c) if err == nil { - resolver.Correct[index] = pack + resolver.Correct = append(resolver.Correct, c) + // resolver.Correct[index] = pack resolver.ToAttempt-- } else { resolver.Attempts-- @@ -221,15 +239,17 @@ func (resolver *QLearningResolver) Reward(action *qlearning.StateAction) float32 // Next creates a new slice of qlearning.Action instances. A possible // action is created for each package that could be removed from the formula's target func (resolver *QLearningResolver) Next() []qlearning.Action { - actions := make([]qlearning.Action, 0, len(resolver.Targets)-1) + actions := make([]qlearning.Action, 0, (len(resolver.Targets)-1)*2) + fmt.Println("Actions") for _, pack := range resolver.Targets { // attempted := resolver.Attempted[pack.String()] // if !attempted { - actions = append(actions, &Choice{Package: pack.String()}) + actions = append(actions, &Choice{Package: pack.String(), Action: ActionRemoved}) + actions = append(actions, &Choice{Package: pack.String(), Action: ActionAdded}) + fmt.Println(pack.GetName(), " -> Action added: Removed - Added") // } } - fmt.Println("ACTIONS", actions) fmt.Println("_______") return actions } @@ -246,12 +266,13 @@ func (resolver *QLearningResolver) Log(msg string, args ...interface{}) { // String returns a consistent hash for the current env state to be // used in a qlearning.Agent. func (resolver *QLearningResolver) String() string { - return fmt.Sprintf("%s", resolver.Correct) + return fmt.Sprintf("%v", resolver.Correct) } // Choice implements qlearning.Action for a package choice for removal from wanted targets type Choice struct { Package string + Action ActionType } // String returns the character for the current action. @@ -262,7 +283,7 @@ func (choice *Choice) String() string { // Apply updates the state of the solver for the package choice. func (choice *Choice) Apply(state qlearning.State) qlearning.State { resolver := state.(*QLearningResolver) - resolver.Choose(choice.Package) + resolver.Choose(*choice) return resolver } From 7ce522110e09ae6bbaddd212f6526c501cf39a6a Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 09:52:38 +0100 Subject: [PATCH 08/30] update vendor/ --- .../github.com/ecooper/qlearning/.gitignore | 24 +++ vendor/github.com/ecooper/qlearning/LICENSE | 21 +++ vendor/github.com/ecooper/qlearning/README.md | 72 ++++++++ .../github.com/ecooper/qlearning/qlearning.go | 167 ++++++++++++++++++ .../{crillab => mudler}/gophersat/LICENSE | 0 .../{crillab => mudler}/gophersat/bf/bf.go | 10 +- .../{crillab => mudler}/gophersat/bf/doc.go | 0 .../gophersat/bf/parser.go | 0 .../gophersat/solver/card.go | 5 +- .../gophersat/solver/clause.go | 0 .../gophersat/solver/clause_alloc.go | 0 .../gophersat/solver/doc.go | 0 .../gophersat/solver/interface.go | 0 .../gophersat/solver/lbd.go | 0 .../gophersat/solver/learn.go | 0 .../gophersat/solver/parser.go | 10 +- .../gophersat/solver/parser_pb.go | 14 +- .../gophersat/solver/pb.go | 0 .../gophersat/solver/preprocess.go | 0 .../gophersat/solver/problem.go | 0 .../gophersat/solver/queue.go | 0 .../gophersat/solver/solver.go | 14 +- .../gophersat/solver/sort.go | 0 .../gophersat/solver/types.go | 0 .../gophersat/solver/watcher.go | 0 vendor/modules.txt | 10 +- 26 files changed, 324 insertions(+), 23 deletions(-) create mode 100644 vendor/github.com/ecooper/qlearning/.gitignore create mode 100644 vendor/github.com/ecooper/qlearning/LICENSE create mode 100644 vendor/github.com/ecooper/qlearning/README.md create mode 100644 vendor/github.com/ecooper/qlearning/qlearning.go rename vendor/github.com/{crillab => mudler}/gophersat/LICENSE (100%) rename vendor/github.com/{crillab => mudler}/gophersat/bf/bf.go (97%) rename vendor/github.com/{crillab => mudler}/gophersat/bf/doc.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/bf/parser.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/card.go (89%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/clause.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/clause_alloc.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/doc.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/interface.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/lbd.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/learn.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/parser.go (96%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/parser_pb.go (95%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/pb.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/preprocess.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/problem.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/queue.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/solver.go (98%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/sort.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/types.go (100%) rename vendor/github.com/{crillab => mudler}/gophersat/solver/watcher.go (100%) diff --git a/vendor/github.com/ecooper/qlearning/.gitignore b/vendor/github.com/ecooper/qlearning/.gitignore new file mode 100644 index 00000000..daf913b1 --- /dev/null +++ b/vendor/github.com/ecooper/qlearning/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/ecooper/qlearning/LICENSE b/vendor/github.com/ecooper/qlearning/LICENSE new file mode 100644 index 00000000..3eef5b3f --- /dev/null +++ b/vendor/github.com/ecooper/qlearning/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Eric Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/ecooper/qlearning/README.md b/vendor/github.com/ecooper/qlearning/README.md new file mode 100644 index 00000000..4bcd1414 --- /dev/null +++ b/vendor/github.com/ecooper/qlearning/README.md @@ -0,0 +1,72 @@ +# qlearning + +The qlearning package provides a series of interfaces and utilities to implement +the [Q-Learning](https://en.wikipedia.org/wiki/Q-learning) algorithm in +Go. + +This project was largely inspired by [flappybird-qlearning- +bot](https://github.com/chncyhn/flappybird-qlearning-bot). + +*Until a release is tagged, qlearning should be considered highly +experimental and mostly a fun toy.* + +## Installation + +```shell +$ go get https://github.com/ecooper/qlearning +``` + +## Quickstart + +qlearning provides example implementations in the [examples](examples/) +directory of the project. + +[hangman.go](examples/hangman.go) provides a naive implementation of +[Hangman](https://en.wikipedia.org/wiki/Hangman_(game)) for use with +qlearning. + +```shell +$ cd $GOPATH/src/github.com/ecooper/qlearning/examples +$ go run hangman.go -h +Usage of hangman: + -debug + Set debug + -games int + Play N games (default 5000000) + -progress int + Print progress messages every N games (default 1000) + -wordlist string + Path to a wordlist (default "./wordlist.txt") + -words int + Use N words from wordlist (default 10000) +``` + +By default, running [hangman.go](examples/hangman.go) will play millions +of games against a 10,000-word corpus. That's a bit overkill for just +trying out qlearning. You can run it against a smaller number of words +for a few number of games using the `-games` and `-words` flags. + +```shell +$ go run hangman.go -words 100 -progress 1000 -games 5000 +100 words loaded +1000 games played: 92 WINS 908 LOSSES 9% WIN RATE +2000 games played: 447 WINS 1553 LOSSES 36% WIN RATE +3000 games played: 1064 WINS 1936 LOSSES 62% WIN RATE +4000 games played: 1913 WINS 2087 LOSSES 85% WIN RATE +5000 games played: 2845 WINS 2155 LOSSES 93% WIN RATE + +Agent performance: 5000 games played, 2845 WINS 2155 LOSSES 57% WIN RATE +``` + +"WIN RATE" per progress report is isolated within that cycle, a group of +1000 games in this example. The win rate is meant to show the velocity +of learning by the agent. If it is "learning", the win rate should be +increasing until reaching convergence. + +As you can see, after 5000 games, the agent is able to "learn" and play +hangman against a 100-word vocabulary. + +## Usage + +See [godocs](https://godoc.org/github.com/ecooper/qlearning) for the +package documentation. diff --git a/vendor/github.com/ecooper/qlearning/qlearning.go b/vendor/github.com/ecooper/qlearning/qlearning.go new file mode 100644 index 00000000..5a36a105 --- /dev/null +++ b/vendor/github.com/ecooper/qlearning/qlearning.go @@ -0,0 +1,167 @@ +// Package qlearning is an experimental set of interfaces and helpers to +// implement the Q-learning algorithm in Go. +// +// This is highly experimental and should be considered a toy. +// +// See https://github.com/ecooper/qlearning/tree/master/examples for +// implementation examples. +package qlearning + +import ( + "fmt" + "math/rand" + "time" +) + +// State is an interface wrapping the current state of the model. +type State interface { + + // String returns a string representation of the given state. + // Implementers should take care to insure that this is a consistent + // hash for a given state. + String() string + + // Next provides a slice of possible Actions that could be applied to + // a state. + Next() []Action +} + +// Action is an interface wrapping an action that can be applied to the +// model's current state. +// +// BUG (ecooper): A state should apply an action, not the other way +// around. +type Action interface { + String() string + Apply(State) State +} + +// Rewarder is an interface wrapping the ability to provide a reward +// for the execution of an action in a given state. +type Rewarder interface { + // Reward calculates the reward value for a given action in a given + // state. + Reward(action *StateAction) float32 +} + +// Agent is an interface for a model's agent and is able to learn +// from actions and return the current Q-value of an action at a given state. +type Agent interface { + // Learn updates the model for a given state and action, using the + // provided Rewarder implementation. + Learn(*StateAction, Rewarder) + + // Value returns the current Q-value for a State and Action. + Value(State, Action) float32 + + // Return a string representation of the Agent. + String() string +} + +// StateAction is a struct grouping an action to a given State. Additionally, +// a Value can be associated to StateAction, which is typically the Q-value. +type StateAction struct { + State State + Action Action + Value float32 +} + +// NewStateAction creates a new StateAction for a State and Action. +func NewStateAction(state State, action Action, val float32) *StateAction { + return &StateAction{ + State: state, + Action: action, + Value: val, + } +} + +// Next uses an Agent and State to find the highest scored Action. +// +// In the case of Q-value ties for a set of actions, a random +// value is selected. +func Next(agent Agent, state State) *StateAction { + best := make([]*StateAction, 0) + bestVal := float32(0.0) + + for _, action := range state.Next() { + val := agent.Value(state, action) + + if bestVal == float32(0.0) { + best = append(best, NewStateAction(state, action, val)) + bestVal = val + } else { + if val > bestVal { + best = []*StateAction{NewStateAction(state, action, val)} + bestVal = val + } else if val == bestVal { + best = append(best, NewStateAction(state, action, val)) + } + } + } + + return best[rand.Intn(len(best))] +} + +// SimpleAgent is an Agent implementation that stores Q-values in a +// map of maps. +type SimpleAgent struct { + q map[string]map[string]float32 + lr float32 + d float32 +} + +// NewSimpleAgent creates a SimpleAgent with the provided learning rate +// and discount factor. +func NewSimpleAgent(lr, d float32) *SimpleAgent { + return &SimpleAgent{ + q: make(map[string]map[string]float32), + d: d, + lr: lr, + } +} + +// getActions returns the current Q-values for a given state. +func (agent *SimpleAgent) getActions(state string) map[string]float32 { + if _, ok := agent.q[state]; !ok { + agent.q[state] = make(map[string]float32) + } + + return agent.q[state] +} + +// Learn updates the existing Q-value for the given State and Action +// using the Rewarder. +// +// See https://en.wikipedia.org/wiki/Q-learning#Algorithm +func (agent *SimpleAgent) Learn(action *StateAction, reward Rewarder) { + current := action.State.String() + next := action.Action.Apply(action.State).String() + + actions := agent.getActions(current) + + maxNextVal := float32(0.0) + for _, v := range agent.getActions(next) { + if v > maxNextVal { + maxNextVal = v + } + } + + currentVal := actions[action.Action.String()] + actions[action.Action.String()] = currentVal + agent.lr*(reward.Reward(action)+agent.d*maxNextVal-currentVal) +} + +// Value gets the current Q-value for a State and Action. +func (agent *SimpleAgent) Value(state State, action Action) float32 { + return agent.getActions(state.String())[action.String()] +} + +// String returns the current Q-value map as a printed string. +// +// BUG (ecooper): This is useless. +func (agent *SimpleAgent) String() string { + return fmt.Sprintf("%v", agent.q) +} + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} diff --git a/vendor/github.com/crillab/gophersat/LICENSE b/vendor/github.com/mudler/gophersat/LICENSE similarity index 100% rename from vendor/github.com/crillab/gophersat/LICENSE rename to vendor/github.com/mudler/gophersat/LICENSE diff --git a/vendor/github.com/crillab/gophersat/bf/bf.go b/vendor/github.com/mudler/gophersat/bf/bf.go similarity index 97% rename from vendor/github.com/crillab/gophersat/bf/bf.go rename to vendor/github.com/mudler/gophersat/bf/bf.go index b80b61c1..bcfc2bd3 100644 --- a/vendor/github.com/crillab/gophersat/bf/bf.go +++ b/vendor/github.com/mudler/gophersat/bf/bf.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/crillab/gophersat/solver" + "github.com/mudler/gophersat/solver" ) // A Formula is any kind of boolean formula, not necessarily in CNF. @@ -72,8 +72,8 @@ type trueConst struct{} // True is the constant denoting a tautology. var True Formula = trueConst{} -func (t trueConst) nnf() Formula { return t } -func (t trueConst) String() string { return "⊤" } +func (t trueConst) nnf() Formula { return t } +func (t trueConst) String() string { return "⊤" } func (t trueConst) Eval(model map[string]bool) bool { return true } // The "false" constant. @@ -82,8 +82,8 @@ type falseConst struct{} // False is the constant denoting a contradiction. var False Formula = falseConst{} -func (f falseConst) nnf() Formula { return f } -func (f falseConst) String() string { return "⊥" } +func (f falseConst) nnf() Formula { return f } +func (f falseConst) String() string { return "⊥" } func (f falseConst) Eval(model map[string]bool) bool { return false } // Var generates a named boolean variable in a formula. diff --git a/vendor/github.com/crillab/gophersat/bf/doc.go b/vendor/github.com/mudler/gophersat/bf/doc.go similarity index 100% rename from vendor/github.com/crillab/gophersat/bf/doc.go rename to vendor/github.com/mudler/gophersat/bf/doc.go diff --git a/vendor/github.com/crillab/gophersat/bf/parser.go b/vendor/github.com/mudler/gophersat/bf/parser.go similarity index 100% rename from vendor/github.com/crillab/gophersat/bf/parser.go rename to vendor/github.com/mudler/gophersat/bf/parser.go diff --git a/vendor/github.com/crillab/gophersat/solver/card.go b/vendor/github.com/mudler/gophersat/solver/card.go similarity index 89% rename from vendor/github.com/crillab/gophersat/solver/card.go rename to vendor/github.com/mudler/gophersat/solver/card.go index bedc5e5c..10fb9a92 100644 --- a/vendor/github.com/crillab/gophersat/solver/card.go +++ b/vendor/github.com/mudler/gophersat/solver/card.go @@ -15,10 +15,11 @@ func AtLeast1(lits ...int) CardConstr { // AtMost1 returns a cardinality constraint stating that at most one of the given lits can be true. func AtMost1(lits ...int) CardConstr { + negated := make([]int, len(lits)) for i, lit := range lits { - lits[i] = -lit + negated[i] = -lit } - return CardConstr{Lits: lits, AtLeast: len(lits) - 1} + return CardConstr{Lits: negated, AtLeast: len(lits) - 1} } // Exactly1 returns two cardinality constraints stating that exactly one of the given lits must be true. diff --git a/vendor/github.com/crillab/gophersat/solver/clause.go b/vendor/github.com/mudler/gophersat/solver/clause.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/clause.go rename to vendor/github.com/mudler/gophersat/solver/clause.go diff --git a/vendor/github.com/crillab/gophersat/solver/clause_alloc.go b/vendor/github.com/mudler/gophersat/solver/clause_alloc.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/clause_alloc.go rename to vendor/github.com/mudler/gophersat/solver/clause_alloc.go diff --git a/vendor/github.com/crillab/gophersat/solver/doc.go b/vendor/github.com/mudler/gophersat/solver/doc.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/doc.go rename to vendor/github.com/mudler/gophersat/solver/doc.go diff --git a/vendor/github.com/crillab/gophersat/solver/interface.go b/vendor/github.com/mudler/gophersat/solver/interface.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/interface.go rename to vendor/github.com/mudler/gophersat/solver/interface.go diff --git a/vendor/github.com/crillab/gophersat/solver/lbd.go b/vendor/github.com/mudler/gophersat/solver/lbd.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/lbd.go rename to vendor/github.com/mudler/gophersat/solver/lbd.go diff --git a/vendor/github.com/crillab/gophersat/solver/learn.go b/vendor/github.com/mudler/gophersat/solver/learn.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/learn.go rename to vendor/github.com/mudler/gophersat/solver/learn.go diff --git a/vendor/github.com/crillab/gophersat/solver/parser.go b/vendor/github.com/mudler/gophersat/solver/parser.go similarity index 96% rename from vendor/github.com/crillab/gophersat/solver/parser.go rename to vendor/github.com/mudler/gophersat/solver/parser.go index d2bd8dfb..050eaa66 100644 --- a/vendor/github.com/crillab/gophersat/solver/parser.go +++ b/vendor/github.com/mudler/gophersat/solver/parser.go @@ -55,17 +55,21 @@ func ParseSlice(cnf [][]int) *Problem { return &pb } } - pb.simplify() + pb.simplify2() return &pb } +func isSpace(b byte) bool { + return b == ' ' || b == '\t' || b == '\n' || b == '\r' +} + // readInt reads an int from r. // 'b' is the last read byte. It can be a space, a '-' or a digit. // The int can be negated. // All spaces before the int value are ignored. // Can return EOF. func readInt(b *byte, r *bufio.Reader) (res int, err error) { - for err == nil && (*b == ' ' || *b == '\t' || *b == '\n' || *b == '\r') { + for err == nil && isSpace(*b) { *b, err = r.ReadByte() } if err == io.EOF { @@ -88,7 +92,7 @@ func readInt(b *byte, r *bufio.Reader) (res int, err error) { } res = 10*res + int(*b-'0') *b, err = r.ReadByte() - if *b == ' ' || *b == '\t' || *b == '\n' || *b == '\r' { + if isSpace(*b) { break } } diff --git a/vendor/github.com/crillab/gophersat/solver/parser_pb.go b/vendor/github.com/mudler/gophersat/solver/parser_pb.go similarity index 95% rename from vendor/github.com/crillab/gophersat/solver/parser_pb.go rename to vendor/github.com/mudler/gophersat/solver/parser_pb.go index 5e3f0f94..45809da1 100644 --- a/vendor/github.com/crillab/gophersat/solver/parser_pb.go +++ b/vendor/github.com/mudler/gophersat/solver/parser_pb.go @@ -65,6 +65,14 @@ func ParseCardConstrs(constrs []CardConstr) *Problem { return &pb } +func (pb *Problem) appendClause(constr PBConstr) { + lits := make([]Lit, len(constr.Lits)) + for j, val := range constr.Lits { + lits[j] = IntToLit(int32(val)) + } + pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, constr.AtLeast)) +} + // ParsePBConstrs parses and returns a PB problem from PBConstr values. func ParsePBConstrs(constrs []PBConstr) *Problem { var pb Problem @@ -100,11 +108,7 @@ func ParsePBConstrs(constrs []PBConstr) *Problem { } } } else { - lits := make([]Lit, len(constr.Lits)) - for j, val := range constr.Lits { - lits[j] = IntToLit(int32(val)) - } - pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, card)) + pb.appendClause(constr) } } pb.Model = make([]decLevel, pb.NbVars) diff --git a/vendor/github.com/crillab/gophersat/solver/pb.go b/vendor/github.com/mudler/gophersat/solver/pb.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/pb.go rename to vendor/github.com/mudler/gophersat/solver/pb.go diff --git a/vendor/github.com/crillab/gophersat/solver/preprocess.go b/vendor/github.com/mudler/gophersat/solver/preprocess.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/preprocess.go rename to vendor/github.com/mudler/gophersat/solver/preprocess.go diff --git a/vendor/github.com/crillab/gophersat/solver/problem.go b/vendor/github.com/mudler/gophersat/solver/problem.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/problem.go rename to vendor/github.com/mudler/gophersat/solver/problem.go diff --git a/vendor/github.com/crillab/gophersat/solver/queue.go b/vendor/github.com/mudler/gophersat/solver/queue.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/queue.go rename to vendor/github.com/mudler/gophersat/solver/queue.go diff --git a/vendor/github.com/crillab/gophersat/solver/solver.go b/vendor/github.com/mudler/gophersat/solver/solver.go similarity index 98% rename from vendor/github.com/crillab/gophersat/solver/solver.go rename to vendor/github.com/mudler/gophersat/solver/solver.go index c6ef3a37..ec014412 100644 --- a/vendor/github.com/crillab/gophersat/solver/solver.go +++ b/vendor/github.com/mudler/gophersat/solver/solver.go @@ -87,10 +87,16 @@ func New(problem *Problem) *Solver { return &Solver{status: Unsat} } nbVars := problem.NbVars + + trailCap := nbVars + if len(problem.Units) > trailCap { + trailCap = len(problem.Units) + } + s := &Solver{ nbVars: nbVars, status: problem.Status, - trail: make([]Lit, len(problem.Units), nbVars), + trail: make([]Lit, len(problem.Units), trailCap), model: problem.Model, activity: make([]float64, nbVars), polarity: make([]bool, nbVars), @@ -343,7 +349,7 @@ func (s *Solver) propagateAndSearch(lit Lit, lvl decLevel) Status { return Indet } if s.Stats.NbConflicts >= s.wl.idxReduce*s.wl.nbMax { - s.wl.idxReduce = (s.Stats.NbConflicts / s.wl.nbMax) + 1 + s.wl.idxReduce = s.Stats.NbConflicts/s.wl.nbMax + 1 s.reduceLearned() s.bumpNbMax() } @@ -738,7 +744,7 @@ func (s *Solver) Optimal(results chan Result, stop chan struct{}) (res Result) { copy(s.lastModel, s.model) // Save this model: it might be the last one cost = 0 for i, lit := range s.minLits { - if (s.model[lit.Var()] > 0) == lit.IsPositive() { + if s.model[lit.Var()] > 0 == lit.IsPositive() { if s.minWeights == nil { cost++ } else { @@ -803,7 +809,7 @@ func (s *Solver) Minimize() int { copy(s.lastModel, s.model) // Save this model: it might be the last one cost = 0 for i, lit := range s.minLits { - if (s.model[lit.Var()] > 0) == lit.IsPositive() { + if s.model[lit.Var()] > 0 == lit.IsPositive() { if s.minWeights == nil { cost++ } else { diff --git a/vendor/github.com/crillab/gophersat/solver/sort.go b/vendor/github.com/mudler/gophersat/solver/sort.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/sort.go rename to vendor/github.com/mudler/gophersat/solver/sort.go diff --git a/vendor/github.com/crillab/gophersat/solver/types.go b/vendor/github.com/mudler/gophersat/solver/types.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/types.go rename to vendor/github.com/mudler/gophersat/solver/types.go diff --git a/vendor/github.com/crillab/gophersat/solver/watcher.go b/vendor/github.com/mudler/gophersat/solver/watcher.go similarity index 100% rename from vendor/github.com/crillab/gophersat/solver/watcher.go rename to vendor/github.com/mudler/gophersat/solver/watcher.go diff --git a/vendor/modules.txt b/vendor/modules.txt index 5957048a..cc28cdaa 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -49,9 +49,6 @@ github.com/containerd/continuity/pathdriver github.com/containerd/continuity/syscallx # github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d github.com/cpuguy83/go-md2man/v2/md2man -# github.com/crillab/gophersat v1.1.7 -github.com/crillab/gophersat/bf -github.com/crillab/gophersat/solver # github.com/cyphar/filepath-securejoin v0.2.2 github.com/cyphar/filepath-securejoin # github.com/docker/distribution v2.7.0+incompatible @@ -93,6 +90,8 @@ github.com/docker/go-units github.com/docker/libnetwork/ipamutils # github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 github.com/docker/libtrust +# github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd +github.com/ecooper/qlearning # github.com/fatih/color v1.7.0 github.com/fatih/color # github.com/fsnotify/fsnotify v1.4.7 @@ -157,6 +156,9 @@ github.com/mattn/go-isatty github.com/mitchellh/mapstructure # github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616 github.com/mudler/docker-companion/api +# github.com/mudler/gophersat v1.1.5-0.20200211080010-645700b4b7c0 +github.com/mudler/gophersat/bf +github.com/mudler/gophersat/solver # github.com/onsi/ginkgo v1.10.1 github.com/onsi/ginkgo github.com/onsi/ginkgo/config @@ -263,8 +265,8 @@ go.uber.org/multierr go.uber.org/tools/update-license # go.uber.org/zap v1.13.0 go.uber.org/zap -go.uber.org/zap/internal/bufferpool go.uber.org/zap/zapcore +go.uber.org/zap/internal/bufferpool go.uber.org/zap/buffer go.uber.org/zap/internal/color go.uber.org/zap/internal/exit From 711c039296e77b1b8087a6744e8154edf1815a5e Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 14:52:24 +0100 Subject: [PATCH 09/30] Reward by observedDelta Keep a record of the observed delta and maximize reward for it. Also add Noop actions which is turned off by default. Let finish the execution also when no solution is found, as we will take the minimum observed delta as result. This is done on purpose to avoid guessing "when" is a good time to stop the agent, as it could be in the middle of picking up a new action which is not the final (but we need limits, we can't let it run forever). --- pkg/solver/resolver.go | 180 +++++++++++++++++++++++------------- pkg/solver/resolver_test.go | 8 +- 2 files changed, 121 insertions(+), 67 deletions(-) diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go index 79ae94e9..b33d53d4 100644 --- a/pkg/solver/resolver.go +++ b/pkg/solver/resolver.go @@ -16,9 +16,14 @@ package solver import ( + "encoding/json" "fmt" "strconv" + "github.com/mudler/luet/pkg/helpers" + . "github.com/mudler/luet/pkg/logger" + "gopkg.in/yaml.v2" + "github.com/ecooper/qlearning" "github.com/mudler/gophersat/bf" pkg "github.com/mudler/luet/pkg/package" @@ -28,11 +33,14 @@ import ( type ActionType int const ( - Solved = 1 + NoAction = 0 + Solved = iota NoSolution = iota Going = iota ActionRemoved = iota ActionAdded = iota + + DoNoop = false ) //. "github.com/mudler/luet/pkg/logger" @@ -62,6 +70,9 @@ type QLearningResolver struct { Targets []pkg.Package Current []pkg.Package + observedDelta int + observedDeltaChoice []pkg.Package + Agent *qlearning.SimpleAgent debug bool @@ -75,14 +86,13 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package resolver.Formula = f // Our agent has a learning rate of 0.7 and discount of 1.0. - resolver.Agent = qlearning.NewSimpleAgent(0.7, 1.0) // FIXME: Remove hardcoded values - resolver.ToAttempt = len(resolver.Solver.(*Solver).Wanted) - 1 // TODO: type assertions must go away - + resolver.Agent = qlearning.NewSimpleAgent(0.7, 1.0) // FIXME: Remove hardcoded values + resolver.ToAttempt = int(helpers.Factorial(uint64(len(resolver.Solver.(*Solver).Wanted)-1) * 3)) // TODO: type assertions must go away + Debug("Attempts:", resolver.ToAttempt) resolver.Targets = resolver.Solver.(*Solver).Wanted + resolver.observedDelta = 999999 - fmt.Println("Targets", resolver.Targets) - - resolver.Attempts = 99 + resolver.Attempts = 9000 resolver.Attempted = make(map[string]bool, len(resolver.Targets)) resolver.Correct = make([]Choice, len(resolver.Targets), len(resolver.Targets)) @@ -100,39 +110,27 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package // Reward doesn't change state so we can check what the // reward would be for this action, and report how the // env changed. - if resolver.Reward(action) > 0.0 { + score := resolver.Reward(action) + Debug("Scored", score) + if score > 0.0 { resolver.Log("%s was correct", action.Action.String()) - resolver.ToAttempt = 0 // We won. As we had one sat, let's take it + //resolver.ToAttempt = 0 // We won. As we had one sat, let's take it } else { resolver.Log("%s was incorrect", action.Action.String()) } } // If we get good result, take it - if resolver.IsComplete() == Solved { - resolver.Log("Victory!") - resolver.Log("removals needed: ", resolver.Correct) - p := []pkg.Package{} - fmt.Println("Targets", resolver.Targets) - // Strip from targets the ones that the agent removed - TARGET: - for _, pack := range resolver.Targets { - for _, w := range resolver.Correct { - if pack.String() == w.String() { - fmt.Println("Skipping", pack.String()) - continue TARGET - } + // Take the result also if we did reached overall maximum attempts + if resolver.IsComplete() == Solved || resolver.IsComplete() == NoSolution { + Debug("Finished") - } - fmt.Println("Appending", pack.String()) + if len(resolver.observedDeltaChoice) != 0 { + Debug("Taking minimum observed choiceset", resolver.observedDeltaChoice) + // Take the minimum delta observed choice result, and consume it (Try sets the wanted list) + resolver.Solver.(*Solver).Wanted = resolver.observedDeltaChoice + } - p = append(p, pack) - } - fmt.Println("Installing") - for _, pack := range p { - fmt.Println(pack.String()) - } - resolver.Solver.(*Solver).Wanted = p return resolver.Solver.Solve() } else { resolver.Log("Resolver couldn't find a solution!") @@ -158,18 +156,24 @@ func (resolver *QLearningResolver) IsComplete() int { } func (resolver *QLearningResolver) Try(c Choice) error { - pack := c.String() + pack := c.Package + packtoAdd := pkg.FromString(pack) resolver.Attempted[pack+strconv.Itoa(int(c.Action))] = true // increase the count s, _ := resolver.Solver.(*Solver) var filtered []pkg.Package switch c.Action { case ActionAdded: - for _, p := range resolver.Targets { + found := false + for _, p := range s.Wanted { if p.String() == pack { - resolver.Solver.(*Solver).Wanted = append(resolver.Solver.(*Solver).Wanted, p) + found = true + break } } + if !found { + resolver.Solver.(*Solver).Wanted = append(resolver.Solver.(*Solver).Wanted, packtoAdd) + } case ActionRemoved: for _, p := range s.Wanted { @@ -179,9 +183,13 @@ func (resolver *QLearningResolver) Try(c Choice) error { } resolver.Solver.(*Solver).Wanted = filtered - default: - return errors.New("Nonvalid action") + case NoAction: + Debug("Chosen to keep current state") + } + Debug("Current test") + for _, current := range resolver.Solver.(*Solver).Wanted { + Debug("-", current.GetName()) } _, err := resolver.Solver.Solve() @@ -194,12 +202,21 @@ func (resolver *QLearningResolver) Try(c Choice) error { // // Choose updates the resolver's state. func (resolver *QLearningResolver) Choose(c Choice) bool { + pack := pkg.FromString(c.Package) + switch c.Action { + case ActionRemoved: + Debug("Chosed to remove ", pack.GetName()) + case ActionAdded: + Debug("Chosed to add ", pack.GetName()) + } err := resolver.Try(c) if err == nil { resolver.Correct = append(resolver.Correct, c) // resolver.Correct[index] = pack resolver.ToAttempt-- + resolver.Attempts-- // Decrease attempts - it's a barrier + } else { resolver.Attempts-- return false @@ -212,24 +229,39 @@ func (resolver *QLearningResolver) Choose(c Choice) bool { // member of the qlearning.Rewarder interface. If the choice will make sat the formula, a positive score is returned. // Otherwise, a static -1000 is returned. func (resolver *QLearningResolver) Reward(action *qlearning.StateAction) float32 { - choice := action.Action.String() + choice := action.Action.(*Choice) - var filtered []pkg.Package + //_, err := resolver.Solver.Solve() + err := resolver.Try(*choice) - //Filter by fingerprint - for _, p := range resolver.Targets { - if p.String() != choice { - filtered = append(filtered, p) - } - } - - resolver.Solver.(*Solver).Wanted = filtered - //resolver.Current = filtered - _, err := resolver.Solver.Solve() - //resolver.Solver.(*Solver).Wanted = resolver.Targets + toBeInstalled := len(resolver.Solver.(*Solver).Wanted) + originalTarget := len(resolver.Targets) + noaction := choice.Action == NoAction + delta := originalTarget - toBeInstalled + Debug("Observed delta", resolver.observedDelta) + Debug("Current delta", delta) if err == nil { - return 24.0 / float32(len(resolver.Attempted)) + // if toBeInstalled == originalTarget { // Base case: all the targets matches (it shouldn't happen, but lets put a higher) + // Debug("Target match, maximum score") + // return 24.0 / float32(len(resolver.Attempted)) + + // } + if DoNoop { + if noaction && toBeInstalled == 0 { // We decided to stay in the current state, and no targets have been chosen + Debug("Penalty, noaction and no installed") + return -100 + } + } + + if delta <= resolver.observedDelta { // Try to maximise observedDelta + resolver.observedDelta = delta + resolver.observedDeltaChoice = resolver.Solver.(*Solver).Wanted // we store it as this is our return value at the end + Debug("Delta reward", delta) + return 24.0 / float32(len(resolver.Attempted)) + } else if toBeInstalled > 0 { // If we installed something, at least give a good score + return 24.0 / float32(len(resolver.Attempted)) + } } @@ -239,18 +271,27 @@ func (resolver *QLearningResolver) Reward(action *qlearning.StateAction) float32 // Next creates a new slice of qlearning.Action instances. A possible // action is created for each package that could be removed from the formula's target func (resolver *QLearningResolver) Next() []qlearning.Action { - actions := make([]qlearning.Action, 0, (len(resolver.Targets)-1)*2) + actions := make([]qlearning.Action, 0, (len(resolver.Targets)-1)*3) - fmt.Println("Actions") +TARGETS: for _, pack := range resolver.Targets { - // attempted := resolver.Attempted[pack.String()] - // if !attempted { - actions = append(actions, &Choice{Package: pack.String(), Action: ActionRemoved}) + for _, current := range resolver.Solver.(*Solver).Wanted { + if current.String() == pack.String() { + actions = append(actions, &Choice{Package: pack.String(), Action: ActionRemoved}) + Debug(pack.GetName(), " -> Action REMOVE") + continue TARGETS + } + + } actions = append(actions, &Choice{Package: pack.String(), Action: ActionAdded}) - fmt.Println(pack.GetName(), " -> Action added: Removed - Added") - // } + Debug(pack.GetName(), " -> Action ADD") + } - fmt.Println("_______") + + if DoNoop { + actions = append(actions, &Choice{Package: "", Action: NoAction}) // NOOP + } + return actions } @@ -259,25 +300,38 @@ func (resolver *QLearningResolver) Next() []qlearning.Action { func (resolver *QLearningResolver) Log(msg string, args ...interface{}) { if resolver.debug { logMsg := fmt.Sprintf("(%d moves, %d remaining attempts) %s\n", len(resolver.Attempted), resolver.Attempts, msg) - fmt.Printf(logMsg, args...) + Debug(fmt.Sprintf(logMsg, args...)) } } // String returns a consistent hash for the current env state to be // used in a qlearning.Agent. func (resolver *QLearningResolver) String() string { - return fmt.Sprintf("%v", resolver.Correct) + return fmt.Sprintf("%v", resolver.Solver.(*Solver).Wanted) } // Choice implements qlearning.Action for a package choice for removal from wanted targets type Choice struct { - Package string - Action ActionType + Package string `json:"pack"` + Action ActionType `json:"action"` +} + +func ChoiceFromString(s string) (*Choice, error) { + var p *Choice + err := yaml.Unmarshal([]byte(s), &p) + if err != nil { + return nil, err + } + return p, nil } // String returns the character for the current action. func (choice *Choice) String() string { - return choice.Package + data, err := json.Marshal(choice) + if err != nil { + return "" + } + return string(data) } // Apply updates the state of the solver for the package choice. diff --git a/pkg/solver/resolver_test.go b/pkg/solver/resolver_test.go index 90fb5ae3..3ef08c29 100644 --- a/pkg/solver/resolver_test.go +++ b/pkg/solver/resolver_test.go @@ -112,12 +112,12 @@ var _ = Describe("Resolver", func() { solution, err := s.Install([]pkg.Package{A, D}) Expect(err).ToNot(HaveOccurred()) - Expect(len(solution)).To(Equal(4)) - Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false})) Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: false})) Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true})) + + Expect(len(solution)).To(Equal(4)) }) It("will find out that we can install D and F by ignoring E and A", func() { @@ -142,14 +142,14 @@ var _ = Describe("Resolver", func() { solution, err := s.Install([]pkg.Package{A, D, E, F}) // D and F should go as they have no deps. A/E should be filtered by QLearn Expect(err).ToNot(HaveOccurred()) - Expect(len(solution)).To(Equal(6)) - Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false})) Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: false})) Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) // Was already installed Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true})) Expect(solution).To(ContainElement(PackageAssert{Package: E, Value: false})) Expect(solution).To(ContainElement(PackageAssert{Package: F, Value: true})) + Expect(len(solution)).To(Equal(6)) + }) }) From 2f6bef14d50f24fac2dc59e2b6bb5d566bbe30e9 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 14:55:49 +0100 Subject: [PATCH 10/30] Revert "update vendor/" This reverts commit 7ce522110e09ae6bbaddd212f6526c501cf39a6a. --- .../{mudler => crillab}/gophersat/LICENSE | 0 .../{mudler => crillab}/gophersat/bf/bf.go | 10 +- .../{mudler => crillab}/gophersat/bf/doc.go | 0 .../gophersat/bf/parser.go | 0 .../gophersat/solver/card.go | 5 +- .../gophersat/solver/clause.go | 0 .../gophersat/solver/clause_alloc.go | 0 .../gophersat/solver/doc.go | 0 .../gophersat/solver/interface.go | 0 .../gophersat/solver/lbd.go | 0 .../gophersat/solver/learn.go | 0 .../gophersat/solver/parser.go | 10 +- .../gophersat/solver/parser_pb.go | 14 +- .../gophersat/solver/pb.go | 0 .../gophersat/solver/preprocess.go | 0 .../gophersat/solver/problem.go | 0 .../gophersat/solver/queue.go | 0 .../gophersat/solver/solver.go | 14 +- .../gophersat/solver/sort.go | 0 .../gophersat/solver/types.go | 0 .../gophersat/solver/watcher.go | 0 .../github.com/ecooper/qlearning/.gitignore | 24 --- vendor/github.com/ecooper/qlearning/LICENSE | 21 --- vendor/github.com/ecooper/qlearning/README.md | 72 -------- .../github.com/ecooper/qlearning/qlearning.go | 167 ------------------ vendor/modules.txt | 10 +- 26 files changed, 23 insertions(+), 324 deletions(-) rename vendor/github.com/{mudler => crillab}/gophersat/LICENSE (100%) rename vendor/github.com/{mudler => crillab}/gophersat/bf/bf.go (97%) rename vendor/github.com/{mudler => crillab}/gophersat/bf/doc.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/bf/parser.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/card.go (89%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/clause.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/clause_alloc.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/doc.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/interface.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/lbd.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/learn.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/parser.go (96%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/parser_pb.go (95%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/pb.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/preprocess.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/problem.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/queue.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/solver.go (98%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/sort.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/types.go (100%) rename vendor/github.com/{mudler => crillab}/gophersat/solver/watcher.go (100%) delete mode 100644 vendor/github.com/ecooper/qlearning/.gitignore delete mode 100644 vendor/github.com/ecooper/qlearning/LICENSE delete mode 100644 vendor/github.com/ecooper/qlearning/README.md delete mode 100644 vendor/github.com/ecooper/qlearning/qlearning.go diff --git a/vendor/github.com/mudler/gophersat/LICENSE b/vendor/github.com/crillab/gophersat/LICENSE similarity index 100% rename from vendor/github.com/mudler/gophersat/LICENSE rename to vendor/github.com/crillab/gophersat/LICENSE diff --git a/vendor/github.com/mudler/gophersat/bf/bf.go b/vendor/github.com/crillab/gophersat/bf/bf.go similarity index 97% rename from vendor/github.com/mudler/gophersat/bf/bf.go rename to vendor/github.com/crillab/gophersat/bf/bf.go index bcfc2bd3..b80b61c1 100644 --- a/vendor/github.com/mudler/gophersat/bf/bf.go +++ b/vendor/github.com/crillab/gophersat/bf/bf.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/mudler/gophersat/solver" + "github.com/crillab/gophersat/solver" ) // A Formula is any kind of boolean formula, not necessarily in CNF. @@ -72,8 +72,8 @@ type trueConst struct{} // True is the constant denoting a tautology. var True Formula = trueConst{} -func (t trueConst) nnf() Formula { return t } -func (t trueConst) String() string { return "⊤" } +func (t trueConst) nnf() Formula { return t } +func (t trueConst) String() string { return "⊤" } func (t trueConst) Eval(model map[string]bool) bool { return true } // The "false" constant. @@ -82,8 +82,8 @@ type falseConst struct{} // False is the constant denoting a contradiction. var False Formula = falseConst{} -func (f falseConst) nnf() Formula { return f } -func (f falseConst) String() string { return "⊥" } +func (f falseConst) nnf() Formula { return f } +func (f falseConst) String() string { return "⊥" } func (f falseConst) Eval(model map[string]bool) bool { return false } // Var generates a named boolean variable in a formula. diff --git a/vendor/github.com/mudler/gophersat/bf/doc.go b/vendor/github.com/crillab/gophersat/bf/doc.go similarity index 100% rename from vendor/github.com/mudler/gophersat/bf/doc.go rename to vendor/github.com/crillab/gophersat/bf/doc.go diff --git a/vendor/github.com/mudler/gophersat/bf/parser.go b/vendor/github.com/crillab/gophersat/bf/parser.go similarity index 100% rename from vendor/github.com/mudler/gophersat/bf/parser.go rename to vendor/github.com/crillab/gophersat/bf/parser.go diff --git a/vendor/github.com/mudler/gophersat/solver/card.go b/vendor/github.com/crillab/gophersat/solver/card.go similarity index 89% rename from vendor/github.com/mudler/gophersat/solver/card.go rename to vendor/github.com/crillab/gophersat/solver/card.go index 10fb9a92..bedc5e5c 100644 --- a/vendor/github.com/mudler/gophersat/solver/card.go +++ b/vendor/github.com/crillab/gophersat/solver/card.go @@ -15,11 +15,10 @@ func AtLeast1(lits ...int) CardConstr { // AtMost1 returns a cardinality constraint stating that at most one of the given lits can be true. func AtMost1(lits ...int) CardConstr { - negated := make([]int, len(lits)) for i, lit := range lits { - negated[i] = -lit + lits[i] = -lit } - return CardConstr{Lits: negated, AtLeast: len(lits) - 1} + return CardConstr{Lits: lits, AtLeast: len(lits) - 1} } // Exactly1 returns two cardinality constraints stating that exactly one of the given lits must be true. diff --git a/vendor/github.com/mudler/gophersat/solver/clause.go b/vendor/github.com/crillab/gophersat/solver/clause.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/clause.go rename to vendor/github.com/crillab/gophersat/solver/clause.go diff --git a/vendor/github.com/mudler/gophersat/solver/clause_alloc.go b/vendor/github.com/crillab/gophersat/solver/clause_alloc.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/clause_alloc.go rename to vendor/github.com/crillab/gophersat/solver/clause_alloc.go diff --git a/vendor/github.com/mudler/gophersat/solver/doc.go b/vendor/github.com/crillab/gophersat/solver/doc.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/doc.go rename to vendor/github.com/crillab/gophersat/solver/doc.go diff --git a/vendor/github.com/mudler/gophersat/solver/interface.go b/vendor/github.com/crillab/gophersat/solver/interface.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/interface.go rename to vendor/github.com/crillab/gophersat/solver/interface.go diff --git a/vendor/github.com/mudler/gophersat/solver/lbd.go b/vendor/github.com/crillab/gophersat/solver/lbd.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/lbd.go rename to vendor/github.com/crillab/gophersat/solver/lbd.go diff --git a/vendor/github.com/mudler/gophersat/solver/learn.go b/vendor/github.com/crillab/gophersat/solver/learn.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/learn.go rename to vendor/github.com/crillab/gophersat/solver/learn.go diff --git a/vendor/github.com/mudler/gophersat/solver/parser.go b/vendor/github.com/crillab/gophersat/solver/parser.go similarity index 96% rename from vendor/github.com/mudler/gophersat/solver/parser.go rename to vendor/github.com/crillab/gophersat/solver/parser.go index 050eaa66..d2bd8dfb 100644 --- a/vendor/github.com/mudler/gophersat/solver/parser.go +++ b/vendor/github.com/crillab/gophersat/solver/parser.go @@ -55,21 +55,17 @@ func ParseSlice(cnf [][]int) *Problem { return &pb } } - pb.simplify2() + pb.simplify() return &pb } -func isSpace(b byte) bool { - return b == ' ' || b == '\t' || b == '\n' || b == '\r' -} - // readInt reads an int from r. // 'b' is the last read byte. It can be a space, a '-' or a digit. // The int can be negated. // All spaces before the int value are ignored. // Can return EOF. func readInt(b *byte, r *bufio.Reader) (res int, err error) { - for err == nil && isSpace(*b) { + for err == nil && (*b == ' ' || *b == '\t' || *b == '\n' || *b == '\r') { *b, err = r.ReadByte() } if err == io.EOF { @@ -92,7 +88,7 @@ func readInt(b *byte, r *bufio.Reader) (res int, err error) { } res = 10*res + int(*b-'0') *b, err = r.ReadByte() - if isSpace(*b) { + if *b == ' ' || *b == '\t' || *b == '\n' || *b == '\r' { break } } diff --git a/vendor/github.com/mudler/gophersat/solver/parser_pb.go b/vendor/github.com/crillab/gophersat/solver/parser_pb.go similarity index 95% rename from vendor/github.com/mudler/gophersat/solver/parser_pb.go rename to vendor/github.com/crillab/gophersat/solver/parser_pb.go index 45809da1..5e3f0f94 100644 --- a/vendor/github.com/mudler/gophersat/solver/parser_pb.go +++ b/vendor/github.com/crillab/gophersat/solver/parser_pb.go @@ -65,14 +65,6 @@ func ParseCardConstrs(constrs []CardConstr) *Problem { return &pb } -func (pb *Problem) appendClause(constr PBConstr) { - lits := make([]Lit, len(constr.Lits)) - for j, val := range constr.Lits { - lits[j] = IntToLit(int32(val)) - } - pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, constr.AtLeast)) -} - // ParsePBConstrs parses and returns a PB problem from PBConstr values. func ParsePBConstrs(constrs []PBConstr) *Problem { var pb Problem @@ -108,7 +100,11 @@ func ParsePBConstrs(constrs []PBConstr) *Problem { } } } else { - pb.appendClause(constr) + lits := make([]Lit, len(constr.Lits)) + for j, val := range constr.Lits { + lits[j] = IntToLit(int32(val)) + } + pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, card)) } } pb.Model = make([]decLevel, pb.NbVars) diff --git a/vendor/github.com/mudler/gophersat/solver/pb.go b/vendor/github.com/crillab/gophersat/solver/pb.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/pb.go rename to vendor/github.com/crillab/gophersat/solver/pb.go diff --git a/vendor/github.com/mudler/gophersat/solver/preprocess.go b/vendor/github.com/crillab/gophersat/solver/preprocess.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/preprocess.go rename to vendor/github.com/crillab/gophersat/solver/preprocess.go diff --git a/vendor/github.com/mudler/gophersat/solver/problem.go b/vendor/github.com/crillab/gophersat/solver/problem.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/problem.go rename to vendor/github.com/crillab/gophersat/solver/problem.go diff --git a/vendor/github.com/mudler/gophersat/solver/queue.go b/vendor/github.com/crillab/gophersat/solver/queue.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/queue.go rename to vendor/github.com/crillab/gophersat/solver/queue.go diff --git a/vendor/github.com/mudler/gophersat/solver/solver.go b/vendor/github.com/crillab/gophersat/solver/solver.go similarity index 98% rename from vendor/github.com/mudler/gophersat/solver/solver.go rename to vendor/github.com/crillab/gophersat/solver/solver.go index ec014412..c6ef3a37 100644 --- a/vendor/github.com/mudler/gophersat/solver/solver.go +++ b/vendor/github.com/crillab/gophersat/solver/solver.go @@ -87,16 +87,10 @@ func New(problem *Problem) *Solver { return &Solver{status: Unsat} } nbVars := problem.NbVars - - trailCap := nbVars - if len(problem.Units) > trailCap { - trailCap = len(problem.Units) - } - s := &Solver{ nbVars: nbVars, status: problem.Status, - trail: make([]Lit, len(problem.Units), trailCap), + trail: make([]Lit, len(problem.Units), nbVars), model: problem.Model, activity: make([]float64, nbVars), polarity: make([]bool, nbVars), @@ -349,7 +343,7 @@ func (s *Solver) propagateAndSearch(lit Lit, lvl decLevel) Status { return Indet } if s.Stats.NbConflicts >= s.wl.idxReduce*s.wl.nbMax { - s.wl.idxReduce = s.Stats.NbConflicts/s.wl.nbMax + 1 + s.wl.idxReduce = (s.Stats.NbConflicts / s.wl.nbMax) + 1 s.reduceLearned() s.bumpNbMax() } @@ -744,7 +738,7 @@ func (s *Solver) Optimal(results chan Result, stop chan struct{}) (res Result) { copy(s.lastModel, s.model) // Save this model: it might be the last one cost = 0 for i, lit := range s.minLits { - if s.model[lit.Var()] > 0 == lit.IsPositive() { + if (s.model[lit.Var()] > 0) == lit.IsPositive() { if s.minWeights == nil { cost++ } else { @@ -809,7 +803,7 @@ func (s *Solver) Minimize() int { copy(s.lastModel, s.model) // Save this model: it might be the last one cost = 0 for i, lit := range s.minLits { - if s.model[lit.Var()] > 0 == lit.IsPositive() { + if (s.model[lit.Var()] > 0) == lit.IsPositive() { if s.minWeights == nil { cost++ } else { diff --git a/vendor/github.com/mudler/gophersat/solver/sort.go b/vendor/github.com/crillab/gophersat/solver/sort.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/sort.go rename to vendor/github.com/crillab/gophersat/solver/sort.go diff --git a/vendor/github.com/mudler/gophersat/solver/types.go b/vendor/github.com/crillab/gophersat/solver/types.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/types.go rename to vendor/github.com/crillab/gophersat/solver/types.go diff --git a/vendor/github.com/mudler/gophersat/solver/watcher.go b/vendor/github.com/crillab/gophersat/solver/watcher.go similarity index 100% rename from vendor/github.com/mudler/gophersat/solver/watcher.go rename to vendor/github.com/crillab/gophersat/solver/watcher.go diff --git a/vendor/github.com/ecooper/qlearning/.gitignore b/vendor/github.com/ecooper/qlearning/.gitignore deleted file mode 100644 index daf913b1..00000000 --- a/vendor/github.com/ecooper/qlearning/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/vendor/github.com/ecooper/qlearning/LICENSE b/vendor/github.com/ecooper/qlearning/LICENSE deleted file mode 100644 index 3eef5b3f..00000000 --- a/vendor/github.com/ecooper/qlearning/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016 Eric Cooper - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/ecooper/qlearning/README.md b/vendor/github.com/ecooper/qlearning/README.md deleted file mode 100644 index 4bcd1414..00000000 --- a/vendor/github.com/ecooper/qlearning/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# qlearning - -The qlearning package provides a series of interfaces and utilities to implement -the [Q-Learning](https://en.wikipedia.org/wiki/Q-learning) algorithm in -Go. - -This project was largely inspired by [flappybird-qlearning- -bot](https://github.com/chncyhn/flappybird-qlearning-bot). - -*Until a release is tagged, qlearning should be considered highly -experimental and mostly a fun toy.* - -## Installation - -```shell -$ go get https://github.com/ecooper/qlearning -``` - -## Quickstart - -qlearning provides example implementations in the [examples](examples/) -directory of the project. - -[hangman.go](examples/hangman.go) provides a naive implementation of -[Hangman](https://en.wikipedia.org/wiki/Hangman_(game)) for use with -qlearning. - -```shell -$ cd $GOPATH/src/github.com/ecooper/qlearning/examples -$ go run hangman.go -h -Usage of hangman: - -debug - Set debug - -games int - Play N games (default 5000000) - -progress int - Print progress messages every N games (default 1000) - -wordlist string - Path to a wordlist (default "./wordlist.txt") - -words int - Use N words from wordlist (default 10000) -``` - -By default, running [hangman.go](examples/hangman.go) will play millions -of games against a 10,000-word corpus. That's a bit overkill for just -trying out qlearning. You can run it against a smaller number of words -for a few number of games using the `-games` and `-words` flags. - -```shell -$ go run hangman.go -words 100 -progress 1000 -games 5000 -100 words loaded -1000 games played: 92 WINS 908 LOSSES 9% WIN RATE -2000 games played: 447 WINS 1553 LOSSES 36% WIN RATE -3000 games played: 1064 WINS 1936 LOSSES 62% WIN RATE -4000 games played: 1913 WINS 2087 LOSSES 85% WIN RATE -5000 games played: 2845 WINS 2155 LOSSES 93% WIN RATE - -Agent performance: 5000 games played, 2845 WINS 2155 LOSSES 57% WIN RATE -``` - -"WIN RATE" per progress report is isolated within that cycle, a group of -1000 games in this example. The win rate is meant to show the velocity -of learning by the agent. If it is "learning", the win rate should be -increasing until reaching convergence. - -As you can see, after 5000 games, the agent is able to "learn" and play -hangman against a 100-word vocabulary. - -## Usage - -See [godocs](https://godoc.org/github.com/ecooper/qlearning) for the -package documentation. diff --git a/vendor/github.com/ecooper/qlearning/qlearning.go b/vendor/github.com/ecooper/qlearning/qlearning.go deleted file mode 100644 index 5a36a105..00000000 --- a/vendor/github.com/ecooper/qlearning/qlearning.go +++ /dev/null @@ -1,167 +0,0 @@ -// Package qlearning is an experimental set of interfaces and helpers to -// implement the Q-learning algorithm in Go. -// -// This is highly experimental and should be considered a toy. -// -// See https://github.com/ecooper/qlearning/tree/master/examples for -// implementation examples. -package qlearning - -import ( - "fmt" - "math/rand" - "time" -) - -// State is an interface wrapping the current state of the model. -type State interface { - - // String returns a string representation of the given state. - // Implementers should take care to insure that this is a consistent - // hash for a given state. - String() string - - // Next provides a slice of possible Actions that could be applied to - // a state. - Next() []Action -} - -// Action is an interface wrapping an action that can be applied to the -// model's current state. -// -// BUG (ecooper): A state should apply an action, not the other way -// around. -type Action interface { - String() string - Apply(State) State -} - -// Rewarder is an interface wrapping the ability to provide a reward -// for the execution of an action in a given state. -type Rewarder interface { - // Reward calculates the reward value for a given action in a given - // state. - Reward(action *StateAction) float32 -} - -// Agent is an interface for a model's agent and is able to learn -// from actions and return the current Q-value of an action at a given state. -type Agent interface { - // Learn updates the model for a given state and action, using the - // provided Rewarder implementation. - Learn(*StateAction, Rewarder) - - // Value returns the current Q-value for a State and Action. - Value(State, Action) float32 - - // Return a string representation of the Agent. - String() string -} - -// StateAction is a struct grouping an action to a given State. Additionally, -// a Value can be associated to StateAction, which is typically the Q-value. -type StateAction struct { - State State - Action Action - Value float32 -} - -// NewStateAction creates a new StateAction for a State and Action. -func NewStateAction(state State, action Action, val float32) *StateAction { - return &StateAction{ - State: state, - Action: action, - Value: val, - } -} - -// Next uses an Agent and State to find the highest scored Action. -// -// In the case of Q-value ties for a set of actions, a random -// value is selected. -func Next(agent Agent, state State) *StateAction { - best := make([]*StateAction, 0) - bestVal := float32(0.0) - - for _, action := range state.Next() { - val := agent.Value(state, action) - - if bestVal == float32(0.0) { - best = append(best, NewStateAction(state, action, val)) - bestVal = val - } else { - if val > bestVal { - best = []*StateAction{NewStateAction(state, action, val)} - bestVal = val - } else if val == bestVal { - best = append(best, NewStateAction(state, action, val)) - } - } - } - - return best[rand.Intn(len(best))] -} - -// SimpleAgent is an Agent implementation that stores Q-values in a -// map of maps. -type SimpleAgent struct { - q map[string]map[string]float32 - lr float32 - d float32 -} - -// NewSimpleAgent creates a SimpleAgent with the provided learning rate -// and discount factor. -func NewSimpleAgent(lr, d float32) *SimpleAgent { - return &SimpleAgent{ - q: make(map[string]map[string]float32), - d: d, - lr: lr, - } -} - -// getActions returns the current Q-values for a given state. -func (agent *SimpleAgent) getActions(state string) map[string]float32 { - if _, ok := agent.q[state]; !ok { - agent.q[state] = make(map[string]float32) - } - - return agent.q[state] -} - -// Learn updates the existing Q-value for the given State and Action -// using the Rewarder. -// -// See https://en.wikipedia.org/wiki/Q-learning#Algorithm -func (agent *SimpleAgent) Learn(action *StateAction, reward Rewarder) { - current := action.State.String() - next := action.Action.Apply(action.State).String() - - actions := agent.getActions(current) - - maxNextVal := float32(0.0) - for _, v := range agent.getActions(next) { - if v > maxNextVal { - maxNextVal = v - } - } - - currentVal := actions[action.Action.String()] - actions[action.Action.String()] = currentVal + agent.lr*(reward.Reward(action)+agent.d*maxNextVal-currentVal) -} - -// Value gets the current Q-value for a State and Action. -func (agent *SimpleAgent) Value(state State, action Action) float32 { - return agent.getActions(state.String())[action.String()] -} - -// String returns the current Q-value map as a printed string. -// -// BUG (ecooper): This is useless. -func (agent *SimpleAgent) String() string { - return fmt.Sprintf("%v", agent.q) -} - -func init() { - rand.Seed(time.Now().UTC().UnixNano()) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index cc28cdaa..5957048a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -49,6 +49,9 @@ github.com/containerd/continuity/pathdriver github.com/containerd/continuity/syscallx # github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d github.com/cpuguy83/go-md2man/v2/md2man +# github.com/crillab/gophersat v1.1.7 +github.com/crillab/gophersat/bf +github.com/crillab/gophersat/solver # github.com/cyphar/filepath-securejoin v0.2.2 github.com/cyphar/filepath-securejoin # github.com/docker/distribution v2.7.0+incompatible @@ -90,8 +93,6 @@ github.com/docker/go-units github.com/docker/libnetwork/ipamutils # github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 github.com/docker/libtrust -# github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd -github.com/ecooper/qlearning # github.com/fatih/color v1.7.0 github.com/fatih/color # github.com/fsnotify/fsnotify v1.4.7 @@ -156,9 +157,6 @@ github.com/mattn/go-isatty github.com/mitchellh/mapstructure # github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616 github.com/mudler/docker-companion/api -# github.com/mudler/gophersat v1.1.5-0.20200211080010-645700b4b7c0 -github.com/mudler/gophersat/bf -github.com/mudler/gophersat/solver # github.com/onsi/ginkgo v1.10.1 github.com/onsi/ginkgo github.com/onsi/ginkgo/config @@ -265,8 +263,8 @@ go.uber.org/multierr go.uber.org/tools/update-license # go.uber.org/zap v1.13.0 go.uber.org/zap -go.uber.org/zap/zapcore go.uber.org/zap/internal/bufferpool +go.uber.org/zap/zapcore go.uber.org/zap/buffer go.uber.org/zap/internal/color go.uber.org/zap/internal/exit From 7e0ea34b8185d23558075f952c5316442015676a Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 14:58:17 +0100 Subject: [PATCH 11/30] Switch back to gophersat --- go.mod | 2 +- go.sum | 4 ++-- pkg/package/package.go | 2 +- pkg/solver/resolver.go | 2 +- pkg/solver/solver.go | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index ad505d74..eb00521f 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154 github.com/briandowns/spinner v1.7.0 github.com/cavaliercoder/grab v2.0.0+incompatible + github.com/crillab/gophersat v1.1.7 github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23 github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd github.com/ghodss/yaml v1.0.0 @@ -20,7 +21,6 @@ require ( github.com/marcsauter/single v0.0.0-20181104081128-f8bf46f26ec0 github.com/mattn/go-isatty v0.0.10 // indirect github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616 - github.com/mudler/gophersat v1.1.5-0.20200211080010-645700b4b7c0 github.com/onsi/ginkgo v1.10.1 github.com/onsi/gomega v1.7.0 github.com/otiai10/copy v1.0.2 diff --git a/go.sum b/go.sum index 46c9eb1b..0c447d04 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/crillab/gophersat v1.1.7 h1:f2Phe0W9jGyN1OefygKdcTdNM99q/goSjbWrFRjZGWc= +github.com/crillab/gophersat v1.1.7/go.mod h1:S91tHga1PCZzYhCkStwZAhvp1rCc+zqtSi55I+vDWGc= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -175,8 +177,6 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616 h1:JwaU8XCtGoY43bo15sKp7qpJ5eaa5vFtRW/oE8yUY/4= github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616/go.mod h1:5uUdicxxQ0H591kxsvbZtgjY11OWVUuDShh08gg60sI= -github.com/mudler/gophersat v1.1.5-0.20200211080010-645700b4b7c0 h1:isVUEy1f9OHq+nvWVKfSifqC5aKktrQsnkuVUkjKKGU= -github.com/mudler/gophersat v1.1.5-0.20200211080010-645700b4b7c0/go.mod h1:nT09AIliwEfZq3cJE+u90vftWDejp1H060GuqS//aHk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/pkg/package/package.go b/pkg/package/package.go index 2b096e42..dfde4847 100644 --- a/pkg/package/package.go +++ b/pkg/package/package.go @@ -25,7 +25,7 @@ import ( // . "github.com/mudler/luet/pkg/logger" - "github.com/mudler/gophersat/bf" + "github.com/crillab/gophersat/bf" version "github.com/hashicorp/go-version" "github.com/jinzhu/copier" diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go index b33d53d4..dc85db9c 100644 --- a/pkg/solver/resolver.go +++ b/pkg/solver/resolver.go @@ -20,12 +20,12 @@ import ( "fmt" "strconv" + "github.com/crillab/gophersat/bf" "github.com/mudler/luet/pkg/helpers" . "github.com/mudler/luet/pkg/logger" "gopkg.in/yaml.v2" "github.com/ecooper/qlearning" - "github.com/mudler/gophersat/bf" pkg "github.com/mudler/luet/pkg/package" "github.com/pkg/errors" ) diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index 8b9f1ce6..3abf5846 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -20,7 +20,7 @@ import ( //. "github.com/mudler/luet/pkg/logger" "github.com/pkg/errors" - "github.com/mudler/gophersat/bf" + "github.com/crillab/gophersat/bf" pkg "github.com/mudler/luet/pkg/package" ) From c9090ef1fdd2b151efcc8030abca3a9116e137eb Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 14:59:50 +0100 Subject: [PATCH 12/30] Bump gophersat Pin to master which now includes https://github.com/crillab/gophersat/pull/17 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index eb00521f..45632469 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154 github.com/briandowns/spinner v1.7.0 github.com/cavaliercoder/grab v2.0.0+incompatible - github.com/crillab/gophersat v1.1.7 + github.com/crillab/gophersat v1.1.9-0.20200211102949-9a8bf7f2f0a3 github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23 github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd github.com/ghodss/yaml v1.0.0 diff --git a/go.sum b/go.sum index 0c447d04..67e3cfa2 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/crillab/gophersat v1.1.7 h1:f2Phe0W9jGyN1OefygKdcTdNM99q/goSjbWrFRjZGWc= github.com/crillab/gophersat v1.1.7/go.mod h1:S91tHga1PCZzYhCkStwZAhvp1rCc+zqtSi55I+vDWGc= +github.com/crillab/gophersat v1.1.9-0.20200211102949-9a8bf7f2f0a3 h1:HO63LCf9kTXQgUnlvFeS2qSDQhZ/cLP8DAJO89CythY= +github.com/crillab/gophersat v1.1.9-0.20200211102949-9a8bf7f2f0a3/go.mod h1:S91tHga1PCZzYhCkStwZAhvp1rCc+zqtSi55I+vDWGc= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 33da68c2ff2bdf1b7cbfb789532df01db9409147 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 15:00:14 +0100 Subject: [PATCH 13/30] update vendor/ --- vendor/github.com/crillab/gophersat/bf/bf.go | 8 +- .../crillab/gophersat/solver/card.go | 5 +- .../crillab/gophersat/solver/parser.go | 10 +- .../crillab/gophersat/solver/parser_pb.go | 14 +- .../github.com/crillab/gophersat/solver/pb.go | 7 +- .../crillab/gophersat/solver/solver.go | 14 +- .../github.com/ecooper/qlearning/.gitignore | 24 +++ vendor/github.com/ecooper/qlearning/LICENSE | 21 +++ vendor/github.com/ecooper/qlearning/README.md | 72 ++++++++ .../github.com/ecooper/qlearning/qlearning.go | 167 ++++++++++++++++++ vendor/modules.txt | 6 +- 11 files changed, 327 insertions(+), 21 deletions(-) create mode 100644 vendor/github.com/ecooper/qlearning/.gitignore create mode 100644 vendor/github.com/ecooper/qlearning/LICENSE create mode 100644 vendor/github.com/ecooper/qlearning/README.md create mode 100644 vendor/github.com/ecooper/qlearning/qlearning.go diff --git a/vendor/github.com/crillab/gophersat/bf/bf.go b/vendor/github.com/crillab/gophersat/bf/bf.go index b80b61c1..5d9552f0 100644 --- a/vendor/github.com/crillab/gophersat/bf/bf.go +++ b/vendor/github.com/crillab/gophersat/bf/bf.go @@ -72,8 +72,8 @@ type trueConst struct{} // True is the constant denoting a tautology. var True Formula = trueConst{} -func (t trueConst) nnf() Formula { return t } -func (t trueConst) String() string { return "⊤" } +func (t trueConst) nnf() Formula { return t } +func (t trueConst) String() string { return "⊤" } func (t trueConst) Eval(model map[string]bool) bool { return true } // The "false" constant. @@ -82,8 +82,8 @@ type falseConst struct{} // False is the constant denoting a contradiction. var False Formula = falseConst{} -func (f falseConst) nnf() Formula { return f } -func (f falseConst) String() string { return "⊥" } +func (f falseConst) nnf() Formula { return f } +func (f falseConst) String() string { return "⊥" } func (f falseConst) Eval(model map[string]bool) bool { return false } // Var generates a named boolean variable in a formula. diff --git a/vendor/github.com/crillab/gophersat/solver/card.go b/vendor/github.com/crillab/gophersat/solver/card.go index bedc5e5c..10fb9a92 100644 --- a/vendor/github.com/crillab/gophersat/solver/card.go +++ b/vendor/github.com/crillab/gophersat/solver/card.go @@ -15,10 +15,11 @@ func AtLeast1(lits ...int) CardConstr { // AtMost1 returns a cardinality constraint stating that at most one of the given lits can be true. func AtMost1(lits ...int) CardConstr { + negated := make([]int, len(lits)) for i, lit := range lits { - lits[i] = -lit + negated[i] = -lit } - return CardConstr{Lits: lits, AtLeast: len(lits) - 1} + return CardConstr{Lits: negated, AtLeast: len(lits) - 1} } // Exactly1 returns two cardinality constraints stating that exactly one of the given lits must be true. diff --git a/vendor/github.com/crillab/gophersat/solver/parser.go b/vendor/github.com/crillab/gophersat/solver/parser.go index d2bd8dfb..050eaa66 100644 --- a/vendor/github.com/crillab/gophersat/solver/parser.go +++ b/vendor/github.com/crillab/gophersat/solver/parser.go @@ -55,17 +55,21 @@ func ParseSlice(cnf [][]int) *Problem { return &pb } } - pb.simplify() + pb.simplify2() return &pb } +func isSpace(b byte) bool { + return b == ' ' || b == '\t' || b == '\n' || b == '\r' +} + // readInt reads an int from r. // 'b' is the last read byte. It can be a space, a '-' or a digit. // The int can be negated. // All spaces before the int value are ignored. // Can return EOF. func readInt(b *byte, r *bufio.Reader) (res int, err error) { - for err == nil && (*b == ' ' || *b == '\t' || *b == '\n' || *b == '\r') { + for err == nil && isSpace(*b) { *b, err = r.ReadByte() } if err == io.EOF { @@ -88,7 +92,7 @@ func readInt(b *byte, r *bufio.Reader) (res int, err error) { } res = 10*res + int(*b-'0') *b, err = r.ReadByte() - if *b == ' ' || *b == '\t' || *b == '\n' || *b == '\r' { + if isSpace(*b) { break } } diff --git a/vendor/github.com/crillab/gophersat/solver/parser_pb.go b/vendor/github.com/crillab/gophersat/solver/parser_pb.go index 5e3f0f94..45809da1 100644 --- a/vendor/github.com/crillab/gophersat/solver/parser_pb.go +++ b/vendor/github.com/crillab/gophersat/solver/parser_pb.go @@ -65,6 +65,14 @@ func ParseCardConstrs(constrs []CardConstr) *Problem { return &pb } +func (pb *Problem) appendClause(constr PBConstr) { + lits := make([]Lit, len(constr.Lits)) + for j, val := range constr.Lits { + lits[j] = IntToLit(int32(val)) + } + pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, constr.AtLeast)) +} + // ParsePBConstrs parses and returns a PB problem from PBConstr values. func ParsePBConstrs(constrs []PBConstr) *Problem { var pb Problem @@ -100,11 +108,7 @@ func ParsePBConstrs(constrs []PBConstr) *Problem { } } } else { - lits := make([]Lit, len(constr.Lits)) - for j, val := range constr.Lits { - lits[j] = IntToLit(int32(val)) - } - pb.Clauses = append(pb.Clauses, NewPBClause(lits, constr.Weights, card)) + pb.appendClause(constr) } } pb.Model = make([]decLevel, pb.NbVars) diff --git a/vendor/github.com/crillab/gophersat/solver/pb.go b/vendor/github.com/crillab/gophersat/solver/pb.go index 0c1841f4..636415ca 100644 --- a/vendor/github.com/crillab/gophersat/solver/pb.go +++ b/vendor/github.com/crillab/gophersat/solver/pb.go @@ -57,12 +57,17 @@ func GtEq(lits []int, weights []int, n int) PBConstr { if len(weights) != 0 && len(lits) != len(weights) { panic("not as many lits as weights") } - for i := range weights { + for i := 0; i < len(weights); i++ { if weights[i] < 0 { weights[i] = -weights[i] n += weights[i] lits[i] = -lits[i] } + if weights[i] == 0 { + weights = append(weights[:i], weights[i+1:]...) + lits = append(lits[:i], lits[i+1:]...) + i-- + } } return PBConstr{Lits: lits, Weights: weights, AtLeast: n} } diff --git a/vendor/github.com/crillab/gophersat/solver/solver.go b/vendor/github.com/crillab/gophersat/solver/solver.go index c6ef3a37..ec014412 100644 --- a/vendor/github.com/crillab/gophersat/solver/solver.go +++ b/vendor/github.com/crillab/gophersat/solver/solver.go @@ -87,10 +87,16 @@ func New(problem *Problem) *Solver { return &Solver{status: Unsat} } nbVars := problem.NbVars + + trailCap := nbVars + if len(problem.Units) > trailCap { + trailCap = len(problem.Units) + } + s := &Solver{ nbVars: nbVars, status: problem.Status, - trail: make([]Lit, len(problem.Units), nbVars), + trail: make([]Lit, len(problem.Units), trailCap), model: problem.Model, activity: make([]float64, nbVars), polarity: make([]bool, nbVars), @@ -343,7 +349,7 @@ func (s *Solver) propagateAndSearch(lit Lit, lvl decLevel) Status { return Indet } if s.Stats.NbConflicts >= s.wl.idxReduce*s.wl.nbMax { - s.wl.idxReduce = (s.Stats.NbConflicts / s.wl.nbMax) + 1 + s.wl.idxReduce = s.Stats.NbConflicts/s.wl.nbMax + 1 s.reduceLearned() s.bumpNbMax() } @@ -738,7 +744,7 @@ func (s *Solver) Optimal(results chan Result, stop chan struct{}) (res Result) { copy(s.lastModel, s.model) // Save this model: it might be the last one cost = 0 for i, lit := range s.minLits { - if (s.model[lit.Var()] > 0) == lit.IsPositive() { + if s.model[lit.Var()] > 0 == lit.IsPositive() { if s.minWeights == nil { cost++ } else { @@ -803,7 +809,7 @@ func (s *Solver) Minimize() int { copy(s.lastModel, s.model) // Save this model: it might be the last one cost = 0 for i, lit := range s.minLits { - if (s.model[lit.Var()] > 0) == lit.IsPositive() { + if s.model[lit.Var()] > 0 == lit.IsPositive() { if s.minWeights == nil { cost++ } else { diff --git a/vendor/github.com/ecooper/qlearning/.gitignore b/vendor/github.com/ecooper/qlearning/.gitignore new file mode 100644 index 00000000..daf913b1 --- /dev/null +++ b/vendor/github.com/ecooper/qlearning/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/ecooper/qlearning/LICENSE b/vendor/github.com/ecooper/qlearning/LICENSE new file mode 100644 index 00000000..3eef5b3f --- /dev/null +++ b/vendor/github.com/ecooper/qlearning/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Eric Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/ecooper/qlearning/README.md b/vendor/github.com/ecooper/qlearning/README.md new file mode 100644 index 00000000..4bcd1414 --- /dev/null +++ b/vendor/github.com/ecooper/qlearning/README.md @@ -0,0 +1,72 @@ +# qlearning + +The qlearning package provides a series of interfaces and utilities to implement +the [Q-Learning](https://en.wikipedia.org/wiki/Q-learning) algorithm in +Go. + +This project was largely inspired by [flappybird-qlearning- +bot](https://github.com/chncyhn/flappybird-qlearning-bot). + +*Until a release is tagged, qlearning should be considered highly +experimental and mostly a fun toy.* + +## Installation + +```shell +$ go get https://github.com/ecooper/qlearning +``` + +## Quickstart + +qlearning provides example implementations in the [examples](examples/) +directory of the project. + +[hangman.go](examples/hangman.go) provides a naive implementation of +[Hangman](https://en.wikipedia.org/wiki/Hangman_(game)) for use with +qlearning. + +```shell +$ cd $GOPATH/src/github.com/ecooper/qlearning/examples +$ go run hangman.go -h +Usage of hangman: + -debug + Set debug + -games int + Play N games (default 5000000) + -progress int + Print progress messages every N games (default 1000) + -wordlist string + Path to a wordlist (default "./wordlist.txt") + -words int + Use N words from wordlist (default 10000) +``` + +By default, running [hangman.go](examples/hangman.go) will play millions +of games against a 10,000-word corpus. That's a bit overkill for just +trying out qlearning. You can run it against a smaller number of words +for a few number of games using the `-games` and `-words` flags. + +```shell +$ go run hangman.go -words 100 -progress 1000 -games 5000 +100 words loaded +1000 games played: 92 WINS 908 LOSSES 9% WIN RATE +2000 games played: 447 WINS 1553 LOSSES 36% WIN RATE +3000 games played: 1064 WINS 1936 LOSSES 62% WIN RATE +4000 games played: 1913 WINS 2087 LOSSES 85% WIN RATE +5000 games played: 2845 WINS 2155 LOSSES 93% WIN RATE + +Agent performance: 5000 games played, 2845 WINS 2155 LOSSES 57% WIN RATE +``` + +"WIN RATE" per progress report is isolated within that cycle, a group of +1000 games in this example. The win rate is meant to show the velocity +of learning by the agent. If it is "learning", the win rate should be +increasing until reaching convergence. + +As you can see, after 5000 games, the agent is able to "learn" and play +hangman against a 100-word vocabulary. + +## Usage + +See [godocs](https://godoc.org/github.com/ecooper/qlearning) for the +package documentation. diff --git a/vendor/github.com/ecooper/qlearning/qlearning.go b/vendor/github.com/ecooper/qlearning/qlearning.go new file mode 100644 index 00000000..5a36a105 --- /dev/null +++ b/vendor/github.com/ecooper/qlearning/qlearning.go @@ -0,0 +1,167 @@ +// Package qlearning is an experimental set of interfaces and helpers to +// implement the Q-learning algorithm in Go. +// +// This is highly experimental and should be considered a toy. +// +// See https://github.com/ecooper/qlearning/tree/master/examples for +// implementation examples. +package qlearning + +import ( + "fmt" + "math/rand" + "time" +) + +// State is an interface wrapping the current state of the model. +type State interface { + + // String returns a string representation of the given state. + // Implementers should take care to insure that this is a consistent + // hash for a given state. + String() string + + // Next provides a slice of possible Actions that could be applied to + // a state. + Next() []Action +} + +// Action is an interface wrapping an action that can be applied to the +// model's current state. +// +// BUG (ecooper): A state should apply an action, not the other way +// around. +type Action interface { + String() string + Apply(State) State +} + +// Rewarder is an interface wrapping the ability to provide a reward +// for the execution of an action in a given state. +type Rewarder interface { + // Reward calculates the reward value for a given action in a given + // state. + Reward(action *StateAction) float32 +} + +// Agent is an interface for a model's agent and is able to learn +// from actions and return the current Q-value of an action at a given state. +type Agent interface { + // Learn updates the model for a given state and action, using the + // provided Rewarder implementation. + Learn(*StateAction, Rewarder) + + // Value returns the current Q-value for a State and Action. + Value(State, Action) float32 + + // Return a string representation of the Agent. + String() string +} + +// StateAction is a struct grouping an action to a given State. Additionally, +// a Value can be associated to StateAction, which is typically the Q-value. +type StateAction struct { + State State + Action Action + Value float32 +} + +// NewStateAction creates a new StateAction for a State and Action. +func NewStateAction(state State, action Action, val float32) *StateAction { + return &StateAction{ + State: state, + Action: action, + Value: val, + } +} + +// Next uses an Agent and State to find the highest scored Action. +// +// In the case of Q-value ties for a set of actions, a random +// value is selected. +func Next(agent Agent, state State) *StateAction { + best := make([]*StateAction, 0) + bestVal := float32(0.0) + + for _, action := range state.Next() { + val := agent.Value(state, action) + + if bestVal == float32(0.0) { + best = append(best, NewStateAction(state, action, val)) + bestVal = val + } else { + if val > bestVal { + best = []*StateAction{NewStateAction(state, action, val)} + bestVal = val + } else if val == bestVal { + best = append(best, NewStateAction(state, action, val)) + } + } + } + + return best[rand.Intn(len(best))] +} + +// SimpleAgent is an Agent implementation that stores Q-values in a +// map of maps. +type SimpleAgent struct { + q map[string]map[string]float32 + lr float32 + d float32 +} + +// NewSimpleAgent creates a SimpleAgent with the provided learning rate +// and discount factor. +func NewSimpleAgent(lr, d float32) *SimpleAgent { + return &SimpleAgent{ + q: make(map[string]map[string]float32), + d: d, + lr: lr, + } +} + +// getActions returns the current Q-values for a given state. +func (agent *SimpleAgent) getActions(state string) map[string]float32 { + if _, ok := agent.q[state]; !ok { + agent.q[state] = make(map[string]float32) + } + + return agent.q[state] +} + +// Learn updates the existing Q-value for the given State and Action +// using the Rewarder. +// +// See https://en.wikipedia.org/wiki/Q-learning#Algorithm +func (agent *SimpleAgent) Learn(action *StateAction, reward Rewarder) { + current := action.State.String() + next := action.Action.Apply(action.State).String() + + actions := agent.getActions(current) + + maxNextVal := float32(0.0) + for _, v := range agent.getActions(next) { + if v > maxNextVal { + maxNextVal = v + } + } + + currentVal := actions[action.Action.String()] + actions[action.Action.String()] = currentVal + agent.lr*(reward.Reward(action)+agent.d*maxNextVal-currentVal) +} + +// Value gets the current Q-value for a State and Action. +func (agent *SimpleAgent) Value(state State, action Action) float32 { + return agent.getActions(state.String())[action.String()] +} + +// String returns the current Q-value map as a printed string. +// +// BUG (ecooper): This is useless. +func (agent *SimpleAgent) String() string { + return fmt.Sprintf("%v", agent.q) +} + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 5957048a..b4d2d0f2 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -49,7 +49,7 @@ github.com/containerd/continuity/pathdriver github.com/containerd/continuity/syscallx # github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d github.com/cpuguy83/go-md2man/v2/md2man -# github.com/crillab/gophersat v1.1.7 +# github.com/crillab/gophersat v1.1.9-0.20200211102949-9a8bf7f2f0a3 github.com/crillab/gophersat/bf github.com/crillab/gophersat/solver # github.com/cyphar/filepath-securejoin v0.2.2 @@ -93,6 +93,8 @@ github.com/docker/go-units github.com/docker/libnetwork/ipamutils # github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 github.com/docker/libtrust +# github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd +github.com/ecooper/qlearning # github.com/fatih/color v1.7.0 github.com/fatih/color # github.com/fsnotify/fsnotify v1.4.7 @@ -263,8 +265,8 @@ go.uber.org/multierr go.uber.org/tools/update-license # go.uber.org/zap v1.13.0 go.uber.org/zap -go.uber.org/zap/internal/bufferpool go.uber.org/zap/zapcore +go.uber.org/zap/internal/bufferpool go.uber.org/zap/buffer go.uber.org/zap/internal/color go.uber.org/zap/internal/exit From c8f4ba0a47da9873b8b91b0faf0a12604a97265f Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 15:00:28 +0100 Subject: [PATCH 14/30] Add Factorial helper --- pkg/helpers/math.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pkg/helpers/math.go diff --git a/pkg/helpers/math.go b/pkg/helpers/math.go new file mode 100644 index 00000000..ab03ef94 --- /dev/null +++ b/pkg/helpers/math.go @@ -0,0 +1,24 @@ +// Copyright © 2020 Ettore Di Giacinto +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package helpers + +func Factorial(n uint64) (result uint64) { + if n > 0 { + result = n * Factorial(n-1) + return result + } + return 1 +} From ea2a60a853e80079146e24d609e2eeddec6983bf Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 15:58:28 +0100 Subject: [PATCH 15/30] Cleanup, drop hardcoded values and use constructors --- pkg/solver/resolver.go | 46 ++++++++++++++++++++++++------------- pkg/solver/resolver_test.go | 4 ++-- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go index dc85db9c..23774262 100644 --- a/pkg/solver/resolver.go +++ b/pkg/solver/resolver.go @@ -41,6 +41,13 @@ const ( ActionAdded = iota DoNoop = false + + ActionDomains = 3 // Bump it if you increase the number of actions + + DefaultMaxAttempts = 9000 + DefaultLearningRate = 0.7 + DefaultDiscount = 1.0 + DefaultInitialObserved = 999999 ) //. "github.com/mudler/luet/pkg/logger" @@ -62,7 +69,6 @@ type QLearningResolver struct { ToAttempt int Attempted map[string]bool - Correct []Choice Solver PackageSolver Formula bf.Formula @@ -74,8 +80,19 @@ type QLearningResolver struct { observedDeltaChoice []pkg.Package Agent *qlearning.SimpleAgent +} - debug bool +func SimpleQLearningSolver() PackageResolver { + return NewQLearningResolver(DefaultLearningRate, DefaultDiscount, DefaultMaxAttempts, DefaultInitialObserved) +} + +// Defaults LearningRate 0.7, Discount 1.0 +func NewQLearningResolver(LearningRate, Discount float32, MaxAttempts, initialObservedDelta int) PackageResolver { + return &QLearningResolver{ + Agent: qlearning.NewSimpleAgent(LearningRate, Discount), + observedDelta: initialObservedDelta, + Attempts: MaxAttempts, + } } func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (PackagesAssertions, error) { @@ -85,18 +102,20 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package defer s.SetResolver(resolver) // Set back ourselves as resolver resolver.Formula = f - // Our agent has a learning rate of 0.7 and discount of 1.0. - resolver.Agent = qlearning.NewSimpleAgent(0.7, 1.0) // FIXME: Remove hardcoded values - resolver.ToAttempt = int(helpers.Factorial(uint64(len(resolver.Solver.(*Solver).Wanted)-1) * 3)) // TODO: type assertions must go away + + // Our agent by default has a learning rate of 0.7 and discount of 1.0. + if resolver.Agent == nil { + resolver.Agent = qlearning.NewSimpleAgent(DefaultLearningRate, DefaultDiscount) // FIXME: Remove hardcoded values + } + + // 3 are the action domains, counting noop regardless if enabled or not + // get the permutations to attempt + resolver.ToAttempt = int(helpers.Factorial(uint64(len(resolver.Solver.(*Solver).Wanted)-1) * ActionDomains)) // TODO: type assertions must go away Debug("Attempts:", resolver.ToAttempt) resolver.Targets = resolver.Solver.(*Solver).Wanted - resolver.observedDelta = 999999 - resolver.Attempts = 9000 resolver.Attempted = make(map[string]bool, len(resolver.Targets)) - resolver.Correct = make([]Choice, len(resolver.Targets), len(resolver.Targets)) - resolver.debug = true for resolver.IsComplete() == Going { // Pick the next move, which is going to be a letter choice. action := qlearning.Next(resolver.Agent, resolver) @@ -114,7 +133,6 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package Debug("Scored", score) if score > 0.0 { resolver.Log("%s was correct", action.Action.String()) - //resolver.ToAttempt = 0 // We won. As we had one sat, let's take it } else { resolver.Log("%s was incorrect", action.Action.String()) } @@ -212,8 +230,6 @@ func (resolver *QLearningResolver) Choose(c Choice) bool { err := resolver.Try(c) if err == nil { - resolver.Correct = append(resolver.Correct, c) - // resolver.Correct[index] = pack resolver.ToAttempt-- resolver.Attempts-- // Decrease attempts - it's a barrier @@ -298,10 +314,8 @@ TARGETS: // Log is a wrapper of fmt.Printf. If Game.debug is true, Log will print // to stdout. func (resolver *QLearningResolver) Log(msg string, args ...interface{}) { - if resolver.debug { - logMsg := fmt.Sprintf("(%d moves, %d remaining attempts) %s\n", len(resolver.Attempted), resolver.Attempts, msg) - Debug(fmt.Sprintf(logMsg, args...)) - } + logMsg := fmt.Sprintf("(%d moves, %d remaining attempts) %s\n", len(resolver.Attempted), resolver.Attempts, msg) + Debug(fmt.Sprintf(logMsg, args...)) } // String returns a consistent hash for the current env state to be diff --git a/pkg/solver/resolver_test.go b/pkg/solver/resolver_test.go index 3ef08c29..242d67a8 100644 --- a/pkg/solver/resolver_test.go +++ b/pkg/solver/resolver_test.go @@ -93,7 +93,7 @@ var _ = Describe("Resolver", func() { }) Context("QLearningResolver", func() { It("will find out that we can install D by ignoring A", func() { - s.SetResolver(&QLearningResolver{}) + s.SetResolver(SimpleQLearningSolver()) C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C}) A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) @@ -121,7 +121,7 @@ var _ = Describe("Resolver", func() { }) It("will find out that we can install D and F by ignoring E and A", func() { - s.SetResolver(&QLearningResolver{}) + s.SetResolver(SimpleQLearningSolver()) C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{}) B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C}) A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{}) From 54b0dce54be025b5256d29bac63328d623b2a23c Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Tue, 11 Feb 2020 16:28:10 +0100 Subject: [PATCH 16/30] Respect argument passed to Untar in helpers --- pkg/helpers/archive.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/helpers/archive.go b/pkg/helpers/archive.go index d898bf0e..42c4b07d 100644 --- a/pkg/helpers/archive.go +++ b/pkg/helpers/archive.go @@ -21,8 +21,6 @@ import ( "os" "path/filepath" - . "github.com/mudler/luet/pkg/config" - "github.com/docker/docker/pkg/archive" ) @@ -61,7 +59,7 @@ func Untar(src, dest string, sameOwner bool) error { } defer in.Close() - if LuetCfg.GetGeneral().SameOwner { + if sameOwner { // PRE: i have root privileged. opts := &archive.TarOptions{ From 4f33eca263cf99ff68f11d71381b54c98295aa63 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 09:22:10 +0100 Subject: [PATCH 17/30] Consume internal attempts in QLearn so resolver can be re-used --- pkg/solver/resolver.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go index 23774262..255d9092 100644 --- a/pkg/solver/resolver.go +++ b/pkg/solver/resolver.go @@ -68,6 +68,9 @@ type QLearningResolver struct { Attempts int ToAttempt int + + attempts int + Attempted map[string]bool Solver PackageSolver @@ -114,6 +117,8 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package Debug("Attempts:", resolver.ToAttempt) resolver.Targets = resolver.Solver.(*Solver).Wanted + resolver.attempts = resolver.Attempts + resolver.Attempted = make(map[string]bool, len(resolver.Targets)) for resolver.IsComplete() == Going { @@ -159,7 +164,7 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package // Returns the current state. func (resolver *QLearningResolver) IsComplete() int { - if resolver.Attempts < 1 { + if resolver.attempts < 1 { resolver.Log("Attempts finished!") return NoSolution } @@ -231,10 +236,9 @@ func (resolver *QLearningResolver) Choose(c Choice) bool { if err == nil { resolver.ToAttempt-- - resolver.Attempts-- // Decrease attempts - it's a barrier - + resolver.attempts-- // Decrease attempts - it's a barrier. We could also do not decrease it here, allowing more attempts to be made } else { - resolver.Attempts-- + resolver.attempts-- return false } @@ -314,7 +318,7 @@ TARGETS: // Log is a wrapper of fmt.Printf. If Game.debug is true, Log will print // to stdout. func (resolver *QLearningResolver) Log(msg string, args ...interface{}) { - logMsg := fmt.Sprintf("(%d moves, %d remaining attempts) %s\n", len(resolver.Attempted), resolver.Attempts, msg) + logMsg := fmt.Sprintf("(%d moves, %d remaining attempts) %s\n", len(resolver.Attempted), resolver.attempts, msg) Debug(fmt.Sprintf(logMsg, args...)) } From dfb6dab9dcb039d10de73fb22fd89635f75ff3a2 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 10:20:07 +0100 Subject: [PATCH 18/30] Move repository helpers under config They are generated after the system config, let the structure provide such information --- cmd/cleanup.go | 6 ++-- cmd/install.go | 2 +- cmd/repo/list.go | 3 +- cmd/search.go | 2 +- cmd/uninstall.go | 2 +- cmd/upgrade.go | 2 +- pkg/config/config.go | 45 +++++++++++++++++++++++++ pkg/helpers/repository.go | 63 ----------------------------------- pkg/installer/client/http.go | 3 +- pkg/installer/client/local.go | 3 +- pkg/installer/installer.go | 1 + pkg/installer/repository.go | 2 +- 12 files changed, 59 insertions(+), 75 deletions(-) delete mode 100644 pkg/helpers/repository.go diff --git a/cmd/cleanup.go b/cmd/cleanup.go index cac7256e..c225d71f 100644 --- a/cmd/cleanup.go +++ b/cmd/cleanup.go @@ -35,9 +35,9 @@ var cleanupCmd = &cobra.Command{ var cleaned int = 0 // Check if cache dir exists - if helpers.Exists(helpers.GetSystemPkgsCacheDirPath()) { + if helpers.Exists(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath()) { - files, err := ioutil.ReadDir(helpers.GetSystemPkgsCacheDirPath()) + files, err := ioutil.ReadDir(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath()) if err != nil { Fatal("Error on read cachedir ", err.Error()) } @@ -52,7 +52,7 @@ var cleanupCmd = &cobra.Command{ } err := os.RemoveAll( - filepath.Join(helpers.GetSystemPkgsCacheDirPath(), file.Name())) + filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), file.Name())) if err != nil { Fatal("Error on removing", file.Name()) } diff --git a/cmd/install.go b/cmd/install.go index 5e7299e4..e06f7f29 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -81,7 +81,7 @@ var installCmd = &cobra.Command{ if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { systemDB = pkg.NewBoltDatabase( - filepath.Join(helpers.GetSystemRepoDatabaseDirPath(), "luet.db")) + filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db")) } else { systemDB = pkg.NewInMemoryDatabase(true) } diff --git a/cmd/repo/list.go b/cmd/repo/list.go index 1fd8aa48..c8c4b727 100644 --- a/cmd/repo/list.go +++ b/cmd/repo/list.go @@ -23,7 +23,6 @@ import ( "time" . "github.com/mudler/luet/pkg/config" - "github.com/mudler/luet/pkg/helpers" installer "github.com/mudler/luet/pkg/installer" . "github.com/logrusorgru/aurora" @@ -69,7 +68,7 @@ func NewRepoListCommand() *cobra.Command { repoText = Yellow(repo.Urls[0]).String() } - repobasedir := helpers.GetRepoDatabaseDirPath(repo.Name) + repobasedir := LuetCfg.GetSystem().GetRepoDatabaseDirPath(repo.Name) if repo.Cached { r := installer.NewSystemRepository(repo) diff --git a/cmd/search.go b/cmd/search.go index 1c4c649d..df0f2227 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -75,7 +75,7 @@ var searchCmd = &cobra.Command{ if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { systemDB = pkg.NewBoltDatabase( - filepath.Join(helpers.GetSystemRepoDatabaseDirPath(), "luet.db")) + filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db")) } else { systemDB = pkg.NewInMemoryDatabase(true) } diff --git a/cmd/uninstall.go b/cmd/uninstall.go index f30ac148..2633290e 100644 --- a/cmd/uninstall.go +++ b/cmd/uninstall.go @@ -64,7 +64,7 @@ var uninstallCmd = &cobra.Command{ if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { systemDB = pkg.NewBoltDatabase( - filepath.Join(helpers.GetSystemRepoDatabaseDirPath(), "luet.db")) + filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db")) } else { systemDB = pkg.NewInMemoryDatabase(true) } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index c44b3d0b..ca1dbe52 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -57,7 +57,7 @@ var upgradeCmd = &cobra.Command{ if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { systemDB = pkg.NewBoltDatabase( - filepath.Join(helpers.GetSystemRepoDatabaseDirPath(), "luet.db")) + filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db")) } else { systemDB = pkg.NewInMemoryDatabase(true) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 226cba71..4e407f7b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -22,7 +22,12 @@ import ( "os/user" "runtime" "time" + "io/ioutil" + "os" + "path/filepath" + pkg "github.com/mudler/luet/pkg/package" + solver "github.com/mudler/luet/pkg/solver" v "github.com/spf13/viper" ) @@ -51,6 +56,46 @@ type LuetSystemConfig struct { PkgsCachePath string `yaml:"pkgs_cache_path" mapstructure:"pkgs_cache_path"` } + +func (sc LuetSystemConfig) GetRepoDatabaseDirPath(name string) string { + dbpath := filepath.Join(sc.Rootfs, sc.DatabasePath) + dbpath = filepath.Join(dbpath, "repos/"+name) + err := os.MkdirAll(dbpath, os.ModePerm) + if err != nil { + panic(err) + } + return dbpath +} + +func (sc LuetSystemConfig) GetSystemRepoDatabaseDirPath() string { + dbpath := filepath.Join(sc.Rootfs, + sc.DatabasePath) + err := os.MkdirAll(dbpath, os.ModePerm) + if err != nil { + panic(err) + } + return dbpath +} + +func (sc LuetSystemConfig) GetSystemPkgsCacheDirPath() (ans string) { + var cachepath string + if sc.PkgsCachePath != "" { + cachepath = sc.PkgsCachePath + } else { + // Create dynamic cache for test suites + cachepath, _ = ioutil.TempDir(os.TempDir(), "cachepkgs") + } + + if filepath.IsAbs(cachepath) { + ans = cachepath + } else { + ans = filepath.Join(sc.GetSystemRepoDatabaseDirPath(), cachepath) + } + + return +} + + type LuetRepository struct { Name string `json:"name" yaml:"name" mapstructure:"name"` Description string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description"` diff --git a/pkg/helpers/repository.go b/pkg/helpers/repository.go deleted file mode 100644 index 370471d0..00000000 --- a/pkg/helpers/repository.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright © 2019 Ettore Di Giacinto -// Daniele Rondina -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation; either version 2 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License along -// with this program; if not, see . - -package helpers - -import ( - "io/ioutil" - "os" - "path/filepath" - - "github.com/mudler/luet/pkg/config" -) - -func GetRepoDatabaseDirPath(name string) string { - dbpath := filepath.Join(config.LuetCfg.GetSystem().Rootfs, config.LuetCfg.GetSystem().DatabasePath) - dbpath = filepath.Join(dbpath, "repos/"+name) - err := os.MkdirAll(dbpath, os.ModePerm) - if err != nil { - panic(err) - } - return dbpath -} - -func GetSystemRepoDatabaseDirPath() string { - dbpath := filepath.Join(config.LuetCfg.GetSystem().Rootfs, - config.LuetCfg.GetSystem().DatabasePath) - err := os.MkdirAll(dbpath, os.ModePerm) - if err != nil { - panic(err) - } - return dbpath -} - -func GetSystemPkgsCacheDirPath() (ans string) { - var cachepath string - if config.LuetCfg.GetSystem().PkgsCachePath != "" { - cachepath = config.LuetCfg.GetSystem().PkgsCachePath - } else { - // Create dynamic cache for test suites - cachepath, _ = ioutil.TempDir(os.TempDir(), "cachepkgs") - } - - if filepath.IsAbs(cachepath) { - ans = cachepath - } else { - ans = filepath.Join(GetSystemRepoDatabaseDirPath(), cachepath) - } - - return -} diff --git a/pkg/installer/client/http.go b/pkg/installer/client/http.go index 95dae4bd..7afa3c12 100644 --- a/pkg/installer/client/http.go +++ b/pkg/installer/client/http.go @@ -28,6 +28,7 @@ import ( "github.com/mudler/luet/pkg/compiler" "github.com/mudler/luet/pkg/helpers" + "github.com/mudler/luet/pkg/config" "github.com/cavaliercoder/grab" ) @@ -70,7 +71,7 @@ func (c *HttpClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Arti var temp string artifactName := path.Base(artifact.GetPath()) - cacheFile := filepath.Join(helpers.GetSystemPkgsCacheDirPath(), artifactName) + cacheFile := filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), artifactName) ok := false // Check if file is already in cache diff --git a/pkg/installer/client/local.go b/pkg/installer/client/local.go index 62799dbe..d85f4722 100644 --- a/pkg/installer/client/local.go +++ b/pkg/installer/client/local.go @@ -22,6 +22,7 @@ import ( "path/filepath" . "github.com/mudler/luet/pkg/logger" + "github.com/mudler/luet/pkg/config" "github.com/mudler/luet/pkg/compiler" "github.com/mudler/luet/pkg/helpers" @@ -39,7 +40,7 @@ func (c *LocalClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Art var err error artifactName := path.Base(artifact.GetPath()) - cacheFile := filepath.Join(helpers.GetSystemPkgsCacheDirPath(), artifactName) + cacheFile := filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), artifactName) // Check if file is already in cache if helpers.Exists(cacheFile) { diff --git a/pkg/installer/installer.go b/pkg/installer/installer.go index 043469b6..1028f9bc 100644 --- a/pkg/installer/installer.go +++ b/pkg/installer/installer.go @@ -25,6 +25,7 @@ import ( "github.com/ghodss/yaml" compiler "github.com/mudler/luet/pkg/compiler" + "github.com/mudler/luet/pkg/config" "github.com/mudler/luet/pkg/helpers" . "github.com/mudler/luet/pkg/logger" pkg "github.com/mudler/luet/pkg/package" diff --git a/pkg/installer/repository.go b/pkg/installer/repository.go index 22eebde2..2b7eb009 100644 --- a/pkg/installer/repository.go +++ b/pkg/installer/repository.go @@ -360,7 +360,7 @@ func (r *LuetSystemRepository) Sync(force bool) (Repository, error) { return nil, errors.Wrap(err, "While downloading "+REPOSITORY_SPECFILE) } - repobasedir := helpers.GetRepoDatabaseDirPath(r.GetName()) + repobasedir := config.LuetCfg.GetSystem().GetRepoDatabaseDirPath(r.GetName()) repo, err := r.ReadSpecFile(file, false) if err != nil { return nil, err From d330fedcc48f464f19fa8f0ad299115664c594fb Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 11:05:13 +0100 Subject: [PATCH 19/30] fmt --- pkg/installer/client/http.go | 2 +- pkg/installer/client/local.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/installer/client/http.go b/pkg/installer/client/http.go index 7afa3c12..99fbb9c9 100644 --- a/pkg/installer/client/http.go +++ b/pkg/installer/client/http.go @@ -27,8 +27,8 @@ import ( . "github.com/mudler/luet/pkg/logger" "github.com/mudler/luet/pkg/compiler" - "github.com/mudler/luet/pkg/helpers" "github.com/mudler/luet/pkg/config" + "github.com/mudler/luet/pkg/helpers" "github.com/cavaliercoder/grab" ) diff --git a/pkg/installer/client/local.go b/pkg/installer/client/local.go index d85f4722..0f7d40e5 100644 --- a/pkg/installer/client/local.go +++ b/pkg/installer/client/local.go @@ -21,8 +21,8 @@ import ( "path" "path/filepath" - . "github.com/mudler/luet/pkg/logger" "github.com/mudler/luet/pkg/config" + . "github.com/mudler/luet/pkg/logger" "github.com/mudler/luet/pkg/compiler" "github.com/mudler/luet/pkg/helpers" From 8951203165faee48327863f4e2d84a431bf0715f Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 11:21:08 +0100 Subject: [PATCH 20/30] Add LuetSolverOptions to tweak solver parameter in Config --- pkg/config/config.go | 56 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 4e407f7b..0e2b6132 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -19,19 +19,20 @@ package config import ( "errors" "fmt" - "os/user" - "runtime" - "time" "io/ioutil" "os" + "os/user" "path/filepath" - pkg "github.com/mudler/luet/pkg/package" + "runtime" + "strings" + "time" solver "github.com/mudler/luet/pkg/solver" v "github.com/spf13/viper" ) var LuetCfg = NewLuetConfig(v.GetViper()) +var AvailableResolvers = strings.Join([]string{solver.QLearningResolverType}, " ") type LuetLoggingConfig struct { Path string `mapstructure:"path"` @@ -49,6 +50,27 @@ type LuetGeneralConfig struct { FatalWarns bool `mapstructure:"fatal_warnings"` } +type ResolverType string +type LuetSolverOptions struct { + Type ResolverType `mapstructure:"type"` + LearnRate float32 `mapstructure:"rate"` + Discount float32 `mapstructure:"discount"` + MaxAttempts int `mapstructure:"max_attempts"` +} + +func (opts LuetSolverOptions) Resolver() solver.PackageResolver { + switch opts.Type { + case solver.QLearningResolverType: + if opts.LearnRate != 0.0 { + return solver.NewQLearningResolver(opts.LearnRate, opts.Discount, opts.MaxAttempts, 999999) + + } + return solver.SimpleQLearningSolver() + } + + return &solver.DummyPackageResolver{} +} + type LuetSystemConfig struct { DatabaseEngine string `yaml:"database_engine" mapstructure:"database_engine"` DatabasePath string `yaml:"database_path" mapstructure:"database_path"` @@ -56,7 +78,6 @@ type LuetSystemConfig struct { PkgsCachePath string `yaml:"pkgs_cache_path" mapstructure:"pkgs_cache_path"` } - func (sc LuetSystemConfig) GetRepoDatabaseDirPath(name string) string { dbpath := filepath.Join(sc.Rootfs, sc.DatabasePath) dbpath = filepath.Join(dbpath, "repos/"+name) @@ -67,7 +88,7 @@ func (sc LuetSystemConfig) GetRepoDatabaseDirPath(name string) string { return dbpath } -func (sc LuetSystemConfig) GetSystemRepoDatabaseDirPath() string { +func (sc LuetSystemConfig) GetSystemRepoDatabaseDirPath() string { dbpath := filepath.Join(sc.Rootfs, sc.DatabasePath) err := os.MkdirAll(dbpath, os.ModePerm) @@ -95,7 +116,6 @@ func (sc LuetSystemConfig) GetSystemPkgsCacheDirPath() (ans string) { return } - type LuetRepository struct { Name string `json:"name" yaml:"name" mapstructure:"name"` Description string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description"` @@ -157,6 +177,7 @@ type LuetConfig struct { Logging LuetLoggingConfig `mapstructure:"logging"` General LuetGeneralConfig `mapstructure:"general"` System LuetSystemConfig `mapstructure:"system"` + Solver LuetSolverOptions `mapstructure:"solver"` RepositoriesConfDir []string `mapstructure:"repos_confdir"` CacheRepositories []LuetRepository `mapstructure:"repetitors"` @@ -199,6 +220,11 @@ func GenDefault(viper *v.Viper) { viper.SetDefault("repos_confdir", []string{"/etc/luet/repos.conf.d"}) viper.SetDefault("cache_repositories", []string{}) viper.SetDefault("system_repositories", []string{}) + + viper.SetDefault("solver.type", "") + viper.SetDefault("solver.rate", 0.7) + viper.SetDefault("solver.discount", 1.0) + viper.SetDefault("solver.max_attempts", 9000) } func (c *LuetConfig) AddSystemRepository(r LuetRepository) { @@ -217,6 +243,10 @@ func (c *LuetConfig) GetSystem() *LuetSystemConfig { return &c.System } +func (c *LuetConfig) GetSolverOptions() *LuetSolverOptions { + return &c.Solver +} + func (c *LuetConfig) GetSystemRepository(name string) (*LuetRepository, error) { var ans *LuetRepository = nil @@ -233,6 +263,18 @@ func (c *LuetConfig) GetSystemRepository(name string) (*LuetRepository, error) { return ans, nil } +func (c *LuetSolverOptions) String() string { + ans := fmt.Sprintf(` +solver: + type: %s + rate: %f + discount: %f + max_attempts: %d`, c.Type, c.LearnRate, c.Discount, + c.MaxAttempts) + + return ans +} + func (c *LuetGeneralConfig) String() string { ans := fmt.Sprintf(` general: From 6aa353edb244004148482fc17845c9fdfac61289 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 11:21:30 +0100 Subject: [PATCH 21/30] Consume SolverOptions in installer --- pkg/installer/installer.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pkg/installer/installer.go b/pkg/installer/installer.go index 1028f9bc..f626dad6 100644 --- a/pkg/installer/installer.go +++ b/pkg/installer/installer.go @@ -35,9 +35,15 @@ import ( "github.com/pkg/errors" ) +type LuetInstallerOptions struct { + SolverOptions config.LuetSolverOptions + Concurrency int +} + type LuetInstaller struct { PackageRepositories Repositories - Concurrency int + + Options LuetInstallerOptions } type ArtifactMatch struct { @@ -87,8 +93,8 @@ func NewLuetFinalizerFromYaml(data []byte) (*LuetFinalizer, error) { return &p, err } -func NewLuetInstaller(concurrency int) Installer { - return &LuetInstaller{Concurrency: concurrency} +func NewLuetInstaller(opts LuetInstallerOptions) Installer { + return &LuetInstaller{Options: opts} } func (l *LuetInstaller) Upgrade(s *System) error { @@ -101,7 +107,7 @@ func (l *LuetInstaller) Upgrade(s *System) error { allRepos := pkg.NewInMemoryDatabase(false) syncedRepos.SyncDatabase(allRepos) // compute a "big" world - solv := solver.NewSolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false)) + solv := solver.NewResolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver()) uninstall, solution, err := solv.Upgrade() if err != nil { return errors.Wrap(err, "Failed solving solution for upgrade") @@ -180,7 +186,8 @@ func (l *LuetInstaller) Install(cp []pkg.Package, s *System) error { allRepos := pkg.NewInMemoryDatabase(false) syncedRepos.SyncDatabase(allRepos) p = syncedRepos.ResolveSelectors(p) - solv := solver.NewSolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false)) + + solv := solver.NewResolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver()) solution, err := solv.Install(p) if err != nil { return errors.Wrap(err, "Failed solving solution for package") @@ -215,7 +222,7 @@ func (l *LuetInstaller) Install(cp []pkg.Package, s *System) error { all := make(chan ArtifactMatch) var wg = new(sync.WaitGroup) - for i := 0; i < l.Concurrency; i++ { + for i := 0; i < l.Options.Concurrency; i++ { wg.Add(1) go l.installerWorker(i, wg, all, s) } @@ -357,8 +364,7 @@ func (l *LuetInstaller) uninstall(p pkg.Package, s *System) error { func (l *LuetInstaller) Uninstall(p pkg.Package, s *System) error { // compute uninstall from all world - remove packages in parallel - run uninstall finalizer (in order) - mark the uninstallation in db // Get installed definition - - solv := solver.NewSolver(s.Database, s.Database, pkg.NewInMemoryDatabase(false)) + solv := solver.NewResolver(s.Database, s.Database, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver()) solution, err := solv.Uninstall(p) if err != nil { return errors.Wrap(err, "Uninstall failed") From 07a154474b6c5d63f68890ff63c63aa435efcfa4 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 11:21:55 +0100 Subject: [PATCH 22/30] Consume SolverOptions in compiler --- pkg/compiler/compiler.go | 4 +++- pkg/compiler/interface.go | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index dc494b45..da255b34 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -42,6 +42,7 @@ type LuetCompiler struct { PullFirst, KeepImg, Clean bool Concurrency int CompressionType CompressionImplementation + Options CompilerOptions } func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase, opt *CompilerOptions) Compiler { @@ -58,6 +59,7 @@ func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase, opt *Compi KeepImg: opt.KeepImg, Concurrency: opt.Concurrency, Clean: opt.Clean, + Options: *opt, } } @@ -459,7 +461,7 @@ func (cs *LuetCompiler) packageFromImage(p CompilationSpec, tag string, keepPerm func (cs *LuetCompiler) ComputeDepTree(p CompilationSpec) (solver.PackagesAssertions, error) { - s := solver.NewSolver(pkg.NewInMemoryDatabase(false), cs.Database, pkg.NewInMemoryDatabase(false)) + s := solver.NewResolver(pkg.NewInMemoryDatabase(false), cs.Database, pkg.NewInMemoryDatabase(false), cs.Options.SolverOptions.Resolver()) solution, err := s.Install([]pkg.Package{p.GetPackage()}) if err != nil { diff --git a/pkg/compiler/interface.go b/pkg/compiler/interface.go index 2672e6c0..42ebb4ea 100644 --- a/pkg/compiler/interface.go +++ b/pkg/compiler/interface.go @@ -18,6 +18,7 @@ package compiler import ( "runtime" + "github.com/mudler/luet/pkg/config" pkg "github.com/mudler/luet/pkg/package" "github.com/mudler/luet/pkg/solver" ) @@ -48,6 +49,8 @@ type CompilerOptions struct { Concurrency int CompressionType CompressionImplementation Clean bool + + SolverOptions config.LuetSolverOptions } func NewDefaultCompilerOptions() *CompilerOptions { From 7e388c6fedc46a3bf54035f351c35fdd69ac4642 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 11:22:23 +0100 Subject: [PATCH 23/30] Cleanup resolver from logger It creates cycle and we don't want to output anything from the computation process. We should handle output in different stages Also create constructor for solver to be able to consume resolvers. --- pkg/solver/resolver.go | 49 +++++++++++------------------------------- pkg/solver/solver.go | 8 ++++++- 2 files changed, 19 insertions(+), 38 deletions(-) diff --git a/pkg/solver/resolver.go b/pkg/solver/resolver.go index 255d9092..21f9e783 100644 --- a/pkg/solver/resolver.go +++ b/pkg/solver/resolver.go @@ -22,7 +22,6 @@ import ( "github.com/crillab/gophersat/bf" "github.com/mudler/luet/pkg/helpers" - . "github.com/mudler/luet/pkg/logger" "gopkg.in/yaml.v2" "github.com/ecooper/qlearning" @@ -48,6 +47,8 @@ const ( DefaultLearningRate = 0.7 DefaultDiscount = 1.0 DefaultInitialObserved = 999999 + + QLearningResolverType = "qlearning" ) //. "github.com/mudler/luet/pkg/logger" @@ -99,6 +100,7 @@ func NewQLearningResolver(LearningRate, Discount float32, MaxAttempts, initialOb } func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (PackagesAssertions, error) { + // Info("Using QLearning solver to resolve conflicts. Please be patient.") resolver.Solver = s s.SetResolver(&DummyPackageResolver{}) // Set dummy. Otherwise the attempts will run again a QLearning instance. @@ -114,7 +116,6 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package // 3 are the action domains, counting noop regardless if enabled or not // get the permutations to attempt resolver.ToAttempt = int(helpers.Factorial(uint64(len(resolver.Solver.(*Solver).Wanted)-1) * ActionDomains)) // TODO: type assertions must go away - Debug("Attempts:", resolver.ToAttempt) resolver.Targets = resolver.Solver.(*Solver).Wanted resolver.attempts = resolver.Attempts @@ -134,29 +135,25 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package // Reward doesn't change state so we can check what the // reward would be for this action, and report how the // env changed. - score := resolver.Reward(action) - Debug("Scored", score) - if score > 0.0 { - resolver.Log("%s was correct", action.Action.String()) - } else { - resolver.Log("%s was incorrect", action.Action.String()) - } + // score := resolver.Reward(action) + // if score > 0.0 { + // resolver.Log("%s was correct", action.Action.String()) + // } else { + // resolver.Log("%s was incorrect", action.Action.String()) + // } } // If we get good result, take it // Take the result also if we did reached overall maximum attempts if resolver.IsComplete() == Solved || resolver.IsComplete() == NoSolution { - Debug("Finished") if len(resolver.observedDeltaChoice) != 0 { - Debug("Taking minimum observed choiceset", resolver.observedDeltaChoice) // Take the minimum delta observed choice result, and consume it (Try sets the wanted list) resolver.Solver.(*Solver).Wanted = resolver.observedDeltaChoice } return resolver.Solver.Solve() } else { - resolver.Log("Resolver couldn't find a solution!") return nil, errors.New("QLearning resolver failed ") } @@ -165,16 +162,13 @@ func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (Package // Returns the current state. func (resolver *QLearningResolver) IsComplete() int { if resolver.attempts < 1 { - resolver.Log("Attempts finished!") return NoSolution } if resolver.ToAttempt > 0 { - resolver.Log("We must continue!") return Going } - resolver.Log("we solved it!") return Solved } @@ -206,13 +200,6 @@ func (resolver *QLearningResolver) Try(c Choice) error { } resolver.Solver.(*Solver).Wanted = filtered - case NoAction: - Debug("Chosen to keep current state") - } - - Debug("Current test") - for _, current := range resolver.Solver.(*Solver).Wanted { - Debug("-", current.GetName()) } _, err := resolver.Solver.Solve() @@ -225,13 +212,8 @@ func (resolver *QLearningResolver) Try(c Choice) error { // // Choose updates the resolver's state. func (resolver *QLearningResolver) Choose(c Choice) bool { - pack := pkg.FromString(c.Package) - switch c.Action { - case ActionRemoved: - Debug("Chosed to remove ", pack.GetName()) - case ActionAdded: - Debug("Chosed to add ", pack.GetName()) - } + //pack := pkg.FromString(c.Package) + err := resolver.Try(c) if err == nil { @@ -258,8 +240,6 @@ func (resolver *QLearningResolver) Reward(action *qlearning.StateAction) float32 originalTarget := len(resolver.Targets) noaction := choice.Action == NoAction delta := originalTarget - toBeInstalled - Debug("Observed delta", resolver.observedDelta) - Debug("Current delta", delta) if err == nil { // if toBeInstalled == originalTarget { // Base case: all the targets matches (it shouldn't happen, but lets put a higher) @@ -269,7 +249,6 @@ func (resolver *QLearningResolver) Reward(action *qlearning.StateAction) float32 // } if DoNoop { if noaction && toBeInstalled == 0 { // We decided to stay in the current state, and no targets have been chosen - Debug("Penalty, noaction and no installed") return -100 } } @@ -277,7 +256,6 @@ func (resolver *QLearningResolver) Reward(action *qlearning.StateAction) float32 if delta <= resolver.observedDelta { // Try to maximise observedDelta resolver.observedDelta = delta resolver.observedDeltaChoice = resolver.Solver.(*Solver).Wanted // we store it as this is our return value at the end - Debug("Delta reward", delta) return 24.0 / float32(len(resolver.Attempted)) } else if toBeInstalled > 0 { // If we installed something, at least give a good score return 24.0 / float32(len(resolver.Attempted)) @@ -298,14 +276,11 @@ TARGETS: for _, current := range resolver.Solver.(*Solver).Wanted { if current.String() == pack.String() { actions = append(actions, &Choice{Package: pack.String(), Action: ActionRemoved}) - Debug(pack.GetName(), " -> Action REMOVE") continue TARGETS } } actions = append(actions, &Choice{Package: pack.String(), Action: ActionAdded}) - Debug(pack.GetName(), " -> Action ADD") - } if DoNoop { @@ -319,7 +294,7 @@ TARGETS: // to stdout. func (resolver *QLearningResolver) Log(msg string, args ...interface{}) { logMsg := fmt.Sprintf("(%d moves, %d remaining attempts) %s\n", len(resolver.Attempted), resolver.attempts, msg) - Debug(fmt.Sprintf(logMsg, args...)) + fmt.Println(fmt.Sprintf(logMsg, args...)) } // String returns a consistent hash for the current env state to be diff --git a/pkg/solver/solver.go b/pkg/solver/solver.go index 3abf5846..b6948ee3 100644 --- a/pkg/solver/solver.go +++ b/pkg/solver/solver.go @@ -52,7 +52,13 @@ type Solver struct { // NewSolver accepts as argument two lists of packages, the first is the initial set, // the second represent all the known packages. func NewSolver(installed pkg.PackageDatabase, definitiondb pkg.PackageDatabase, solverdb pkg.PackageDatabase) PackageSolver { - return &Solver{InstalledDatabase: installed, DefinitionDatabase: definitiondb, SolverDatabase: solverdb, Resolver: &DummyPackageResolver{}} + return NewResolver(installed, definitiondb, solverdb, &DummyPackageResolver{}) +} + +// NewReSolver accepts as argument two lists of packages, the first is the initial set, +// the second represent all the known packages. +func NewResolver(installed pkg.PackageDatabase, definitiondb pkg.PackageDatabase, solverdb pkg.PackageDatabase, re PackageResolver) PackageSolver { + return &Solver{InstalledDatabase: installed, DefinitionDatabase: definitiondb, SolverDatabase: solverdb, Resolver: re} } // SetDefinitionDatabase is a setter for the definition Database From 3cd87abafecd082f5719f00f465a4a337d81c347 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 11:23:38 +0100 Subject: [PATCH 24/30] Consume SolverOptions in cli --- cmd/build.go | 11 +++++++++++ cmd/install.go | 11 +++++++++-- cmd/search.go | 12 ++++++++++-- cmd/uninstall.go | 11 +++++++++-- cmd/upgrade.go | 12 +++++++++--- 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/cmd/build.go b/cmd/build.go index 6972f6de..b3fb27ca 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -45,6 +45,11 @@ var buildCmd = &cobra.Command{ viper.BindPFlag("revdeps", cmd.Flags().Lookup("revdeps")) viper.BindPFlag("all", cmd.Flags().Lookup("all")) viper.BindPFlag("compression", cmd.Flags().Lookup("compression")) + + LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type")) + LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount")) + LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate")) + LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts")) }, Run: func(cmd *cobra.Command, args []string) { @@ -93,6 +98,7 @@ var buildCmd = &cobra.Command{ Fatal("Error: " + err.Error()) } opts := compiler.NewDefaultCompilerOptions() + opts.SolverOptions = *LuetCfg.GetSolverOptions() opts.Clean = clean luetCompiler := compiler.NewLuetCompiler(compilerBackend, generalRecipe.GetDatabase(), opts) luetCompiler.SetConcurrency(concurrency) @@ -177,5 +183,10 @@ func init() { buildCmd.Flags().String("destination", path, "Destination folder") buildCmd.Flags().String("compression", "none", "Compression alg: none, gzip") + buildCmd.Flags().String("solver-type", "", "Solver strategy") + buildCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate") + buildCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate") + buildCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts") + RootCmd.AddCommand(buildCmd) } diff --git a/cmd/install.go b/cmd/install.go index e06f7f29..6a33aeee 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -22,7 +22,6 @@ import ( installer "github.com/mudler/luet/pkg/installer" . "github.com/mudler/luet/pkg/config" - "github.com/mudler/luet/pkg/helpers" . "github.com/mudler/luet/pkg/logger" pkg "github.com/mudler/luet/pkg/package" @@ -36,6 +35,10 @@ var installCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath")) LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target")) + LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type")) + LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount")) + LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate")) + LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts")) }, Long: `Install packages in parallel`, Run: func(cmd *cobra.Command, args []string) { @@ -76,7 +79,7 @@ var installCmd = &cobra.Command{ repos = append(repos, r) } - inst := installer.NewLuetInstaller(LuetCfg.GetGeneral().Concurrency) + inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{Concurrency: LuetCfg.GetGeneral().Concurrency, SolverOptions: *LuetCfg.GetSolverOptions()}) inst.Repositories(repos) if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { @@ -100,6 +103,10 @@ func init() { } installCmd.Flags().String("system-dbpath", path, "System db path") installCmd.Flags().String("system-target", path, "System rootpath") + installCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )") + installCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate") + installCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate") + installCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts") RootCmd.AddCommand(installCmd) } diff --git a/cmd/search.go b/cmd/search.go index df0f2227..255850f3 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -20,7 +20,6 @@ import ( "regexp" . "github.com/mudler/luet/pkg/config" - "github.com/mudler/luet/pkg/helpers" installer "github.com/mudler/luet/pkg/installer" . "github.com/mudler/luet/pkg/logger" pkg "github.com/mudler/luet/pkg/package" @@ -37,6 +36,10 @@ var searchCmd = &cobra.Command{ LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath")) LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target")) viper.BindPFlag("installed", cmd.Flags().Lookup("installed")) + LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type")) + LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount")) + LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate")) + LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts")) }, Run: func(cmd *cobra.Command, args []string) { var systemDB pkg.PackageDatabase @@ -57,7 +60,8 @@ var searchCmd = &cobra.Command{ repos = append(repos, r) } - inst := installer.NewLuetInstaller(LuetCfg.GetGeneral().Concurrency) + inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{Concurrency: LuetCfg.GetGeneral().Concurrency, SolverOptions: *LuetCfg.GetSolverOptions()}) + inst.Repositories(repos) synced, err := inst.SyncRepositories(false) if err != nil { @@ -101,5 +105,9 @@ func init() { searchCmd.Flags().String("system-dbpath", path, "System db path") searchCmd.Flags().String("system-target", path, "System rootpath") searchCmd.Flags().Bool("installed", false, "Search between system packages") + searchCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )") + searchCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate") + searchCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate") + searchCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts") RootCmd.AddCommand(searchCmd) } diff --git a/cmd/uninstall.go b/cmd/uninstall.go index 2633290e..d246900d 100644 --- a/cmd/uninstall.go +++ b/cmd/uninstall.go @@ -20,7 +20,6 @@ import ( "path/filepath" . "github.com/mudler/luet/pkg/config" - "github.com/mudler/luet/pkg/helpers" installer "github.com/mudler/luet/pkg/installer" . "github.com/mudler/luet/pkg/logger" pkg "github.com/mudler/luet/pkg/package" @@ -36,6 +35,10 @@ var uninstallCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath")) LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target")) + LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type")) + LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount")) + LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate")) + LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts")) }, Run: func(cmd *cobra.Command, args []string) { var systemDB pkg.PackageDatabase @@ -60,7 +63,7 @@ var uninstallCmd = &cobra.Command{ Uri: make([]string, 0), } - inst := installer.NewLuetInstaller(LuetCfg.GetGeneral().Concurrency) + inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{Concurrency: LuetCfg.GetGeneral().Concurrency, SolverOptions: *LuetCfg.GetSolverOptions()}) if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { systemDB = pkg.NewBoltDatabase( @@ -84,5 +87,9 @@ func init() { } uninstallCmd.Flags().String("system-dbpath", path, "System db path") uninstallCmd.Flags().String("system-target", path, "System rootpath") + uninstallCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )") + uninstallCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate") + uninstallCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate") + uninstallCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts") RootCmd.AddCommand(uninstallCmd) } diff --git a/cmd/upgrade.go b/cmd/upgrade.go index ca1dbe52..dc49eb93 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -19,7 +19,6 @@ import ( "path/filepath" . "github.com/mudler/luet/pkg/config" - "github.com/mudler/luet/pkg/helpers" installer "github.com/mudler/luet/pkg/installer" . "github.com/mudler/luet/pkg/logger" pkg "github.com/mudler/luet/pkg/package" @@ -33,6 +32,10 @@ var upgradeCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { LuetCfg.Viper.BindPFlag("system.database_path", installCmd.Flags().Lookup("system-dbpath")) LuetCfg.Viper.BindPFlag("system.rootfs", installCmd.Flags().Lookup("system-target")) + LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type")) + LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount")) + LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate")) + LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts")) }, Long: `Upgrades packages in parallel`, Run: func(cmd *cobra.Command, args []string) { @@ -48,7 +51,7 @@ var upgradeCmd = &cobra.Command{ repos = append(repos, r) } - inst := installer.NewLuetInstaller(LuetCfg.GetGeneral().Concurrency) + inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{Concurrency: LuetCfg.GetGeneral().Concurrency, SolverOptions: *LuetCfg.GetSolverOptions()}) inst.Repositories(repos) _, err := inst.SyncRepositories(false) if err != nil { @@ -76,6 +79,9 @@ func init() { } upgradeCmd.Flags().String("system-dbpath", path, "System db path") upgradeCmd.Flags().String("system-target", path, "System rootpath") - + upgradeCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )") + upgradeCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate") + upgradeCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate") + upgradeCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts") RootCmd.AddCommand(upgradeCmd) } From b2a5de92228131feb7ef51afcb29e580e2c80829 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 12:05:28 +0100 Subject: [PATCH 25/30] Adapt installer test to constructor changes --- pkg/installer/installer_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/installer/installer_test.go b/pkg/installer/installer_test.go index 3a01ca2f..3d3a2ceb 100644 --- a/pkg/installer/installer_test.go +++ b/pkg/installer/installer_test.go @@ -99,7 +99,7 @@ var _ = Describe("Installer", func() { Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(fakeroot) // clean up - inst := NewLuetInstaller(1) + inst := NewLuetInstaller(LuetInstallerOptions{Concurrency: 1}) repo2, err := NewLuetSystemRepositoryFromYaml([]byte(` name: "test" type: "disk" @@ -212,7 +212,7 @@ urls: Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(fakeroot) // clean up - inst := NewLuetInstaller(1) + inst := NewLuetInstaller(LuetInstallerOptions{Concurrency: 1}) repo2, err := NewLuetSystemRepositoryFromYaml([]byte(` name: "test" type: "disk" @@ -320,7 +320,7 @@ urls: Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(fakeroot) // clean up - inst := NewLuetInstaller(1) + inst := NewLuetInstaller(LuetInstallerOptions{Concurrency: 1}) repo2, err := NewLuetSystemRepositoryFromYaml([]byte(` name: "test" type: "disk" @@ -435,7 +435,7 @@ urls: Expect(err).ToNot(HaveOccurred()) defer os.RemoveAll(fakeroot) // clean up - inst := NewLuetInstaller(1) + inst := NewLuetInstaller(LuetInstallerOptions{Concurrency: 1}) repo2, err := NewLuetSystemRepositoryFromYaml([]byte(` name: "test" type: "disk" From d6f7c47eaec26aa07ec7d723bf8eb68f624cbd49 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 12:23:37 +0100 Subject: [PATCH 26/30] Add compact string notation for SolverOptions --- pkg/config/config.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 0e2b6132..9f4a06c0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -50,12 +50,11 @@ type LuetGeneralConfig struct { FatalWarns bool `mapstructure:"fatal_warnings"` } -type ResolverType string type LuetSolverOptions struct { - Type ResolverType `mapstructure:"type"` - LearnRate float32 `mapstructure:"rate"` - Discount float32 `mapstructure:"discount"` - MaxAttempts int `mapstructure:"max_attempts"` + Type string `mapstructure:"type"` + LearnRate float32 `mapstructure:"rate"` + Discount float32 `mapstructure:"discount"` + MaxAttempts int `mapstructure:"max_attempts"` } func (opts LuetSolverOptions) Resolver() solver.PackageResolver { @@ -71,6 +70,11 @@ func (opts LuetSolverOptions) Resolver() solver.PackageResolver { return &solver.DummyPackageResolver{} } +func (opts *LuetSolverOptions) CompactString() string { + return fmt.Sprintf("type: %s rate: %f, discount: %f, attempts: %d, initialobserved: %d", + opts.Type, opts.LearnRate, opts.Discount, opts.MaxAttempts, 999999) +} + type LuetSystemConfig struct { DatabaseEngine string `yaml:"database_engine" mapstructure:"database_engine"` DatabasePath string `yaml:"database_path" mapstructure:"database_path"` From a756c802f9d39282fbafb9bf8f9c4b9c467fe506 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 12:24:07 +0100 Subject: [PATCH 27/30] Hook config when generating the SolverOptions, display debug message about solver type --- cmd/build.go | 14 ++++++++++++++ cmd/install.go | 12 ++++++++++++ cmd/search.go | 16 +++++++++++++--- cmd/uninstall.go | 12 ++++++++++++ cmd/upgrade.go | 12 ++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/cmd/build.go b/cmd/build.go index b3fb27ca..dce1314b 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -97,8 +97,22 @@ var buildCmd = &cobra.Command{ if err != nil { Fatal("Error: " + err.Error()) } + + stype := LuetCfg.Viper.GetString("solver.type") + discount := LuetCfg.Viper.GetFloat64("solver.discount") + rate := LuetCfg.Viper.GetFloat64("solver.rate") + attempts := LuetCfg.Viper.GetInt("solver.max_attempts") + + LuetCfg.GetSolverOptions().Type = stype + LuetCfg.GetSolverOptions().LearnRate = float32(rate) + LuetCfg.GetSolverOptions().Discount = float32(discount) + LuetCfg.GetSolverOptions().MaxAttempts = attempts + + Debug("Solver", LuetCfg.GetSolverOptions().CompactString()) + opts := compiler.NewDefaultCompilerOptions() opts.SolverOptions = *LuetCfg.GetSolverOptions() + opts.Clean = clean luetCompiler := compiler.NewLuetCompiler(compilerBackend, generalRecipe.GetDatabase(), opts) luetCompiler.SetConcurrency(concurrency) diff --git a/cmd/install.go b/cmd/install.go index 6a33aeee..bcc0ac2c 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -79,6 +79,18 @@ var installCmd = &cobra.Command{ repos = append(repos, r) } + stype := LuetCfg.Viper.GetString("solver.type") + discount := LuetCfg.Viper.GetFloat64("solver.discount") + rate := LuetCfg.Viper.GetFloat64("solver.rate") + attempts := LuetCfg.Viper.GetInt("solver.max_attempts") + + LuetCfg.GetSolverOptions().Type = stype + LuetCfg.GetSolverOptions().LearnRate = float32(rate) + LuetCfg.GetSolverOptions().Discount = float32(discount) + LuetCfg.GetSolverOptions().MaxAttempts = attempts + + Debug("Solver", LuetCfg.GetSolverOptions().CompactString()) + inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{Concurrency: LuetCfg.GetGeneral().Concurrency, SolverOptions: *LuetCfg.GetSolverOptions()}) inst.Repositories(repos) diff --git a/cmd/search.go b/cmd/search.go index 255850f3..2e97aeab 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -25,7 +25,6 @@ import ( pkg "github.com/mudler/luet/pkg/package" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var searchCmd = &cobra.Command{ @@ -35,7 +34,7 @@ var searchCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath")) LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target")) - viper.BindPFlag("installed", cmd.Flags().Lookup("installed")) + LuetCfg.Viper.BindPFlag("installed", cmd.Flags().Lookup("installed")) LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type")) LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount")) LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate")) @@ -47,7 +46,18 @@ var searchCmd = &cobra.Command{ if len(args) != 1 { Fatal("Wrong number of arguments (expected 1)") } - installed := viper.GetBool("installed") + installed := LuetCfg.Viper.GetBool("installed") + stype := LuetCfg.Viper.GetString("solver.type") + discount := LuetCfg.Viper.GetFloat64("solver.discount") + rate := LuetCfg.Viper.GetFloat64("solver.rate") + attempts := LuetCfg.Viper.GetInt("solver.max_attempts") + + LuetCfg.GetSolverOptions().Type = stype + LuetCfg.GetSolverOptions().LearnRate = float32(rate) + LuetCfg.GetSolverOptions().Discount = float32(discount) + LuetCfg.GetSolverOptions().MaxAttempts = attempts + + Debug("Solver", LuetCfg.GetSolverOptions().CompactString()) if !installed { diff --git a/cmd/uninstall.go b/cmd/uninstall.go index d246900d..e375b03b 100644 --- a/cmd/uninstall.go +++ b/cmd/uninstall.go @@ -63,6 +63,18 @@ var uninstallCmd = &cobra.Command{ Uri: make([]string, 0), } + stype := LuetCfg.Viper.GetString("solver.type") + discount := LuetCfg.Viper.GetFloat64("solver.discount") + rate := LuetCfg.Viper.GetFloat64("solver.rate") + attempts := LuetCfg.Viper.GetInt("solver.max_attempts") + + LuetCfg.GetSolverOptions().Type = stype + LuetCfg.GetSolverOptions().LearnRate = float32(rate) + LuetCfg.GetSolverOptions().Discount = float32(discount) + LuetCfg.GetSolverOptions().MaxAttempts = attempts + + Debug("Solver", LuetCfg.GetSolverOptions().CompactString()) + inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{Concurrency: LuetCfg.GetGeneral().Concurrency, SolverOptions: *LuetCfg.GetSolverOptions()}) if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { diff --git a/cmd/upgrade.go b/cmd/upgrade.go index dc49eb93..0b10bd70 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -51,6 +51,18 @@ var upgradeCmd = &cobra.Command{ repos = append(repos, r) } + stype := LuetCfg.Viper.GetString("solver.type") + discount := LuetCfg.Viper.GetFloat64("solver.discount") + rate := LuetCfg.Viper.GetFloat64("solver.rate") + attempts := LuetCfg.Viper.GetInt("solver.max_attempts") + + LuetCfg.GetSolverOptions().Type = stype + LuetCfg.GetSolverOptions().LearnRate = float32(rate) + LuetCfg.GetSolverOptions().Discount = float32(discount) + LuetCfg.GetSolverOptions().MaxAttempts = attempts + + Debug("Solver", LuetCfg.GetSolverOptions().String()) + inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{Concurrency: LuetCfg.GetGeneral().Concurrency, SolverOptions: *LuetCfg.GetSolverOptions()}) inst.Repositories(repos) _, err := inst.SyncRepositories(false) From d7d05de9fe1b3228e845de5ea4f31dd6a9b61a30 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 13:58:28 +0100 Subject: [PATCH 28/30] Add integration test for qlearning solver --- tests/fixtures/qlearning/a/build.yaml | 8 ++ tests/fixtures/qlearning/a/definition.yaml | 8 ++ tests/fixtures/qlearning/b/build.yaml | 5 ++ tests/fixtures/qlearning/b/definition.yaml | 7 ++ tests/fixtures/qlearning/c/build.yaml | 5 ++ tests/fixtures/qlearning/c/definition.yaml | 3 + tests/fixtures/qlearning/d/build.yaml | 5 ++ tests/fixtures/qlearning/d/definition.yaml | 3 + tests/fixtures/qlearning/e/build.yaml | 9 +++ tests/fixtures/qlearning/e/definition.yaml | 8 ++ tests/fixtures/qlearning/f/build.yaml | 5 ++ tests/fixtures/qlearning/f/definition.yaml | 3 + tests/integration/03_qlearning.sh | 94 ++++++++++++++++++++++ tests/integration/run.sh | 8 +- 14 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/qlearning/a/build.yaml create mode 100644 tests/fixtures/qlearning/a/definition.yaml create mode 100644 tests/fixtures/qlearning/b/build.yaml create mode 100644 tests/fixtures/qlearning/b/definition.yaml create mode 100644 tests/fixtures/qlearning/c/build.yaml create mode 100644 tests/fixtures/qlearning/c/definition.yaml create mode 100644 tests/fixtures/qlearning/d/build.yaml create mode 100644 tests/fixtures/qlearning/d/definition.yaml create mode 100644 tests/fixtures/qlearning/e/build.yaml create mode 100644 tests/fixtures/qlearning/e/definition.yaml create mode 100644 tests/fixtures/qlearning/f/build.yaml create mode 100644 tests/fixtures/qlearning/f/definition.yaml create mode 100755 tests/integration/03_qlearning.sh diff --git a/tests/fixtures/qlearning/a/build.yaml b/tests/fixtures/qlearning/a/build.yaml new file mode 100644 index 00000000..1d2106ca --- /dev/null +++ b/tests/fixtures/qlearning/a/build.yaml @@ -0,0 +1,8 @@ +prelude: + - echo a > /aprelude +steps: + - echo a > /a +requires: +- category: "test" + name: "b" + version: "1.0" diff --git a/tests/fixtures/qlearning/a/definition.yaml b/tests/fixtures/qlearning/a/definition.yaml new file mode 100644 index 00000000..d237a187 --- /dev/null +++ b/tests/fixtures/qlearning/a/definition.yaml @@ -0,0 +1,8 @@ +category: "test" +name: "a" +version: "1.0" +requires: +- category: "test" + name: "b" + version: "1.0" + diff --git a/tests/fixtures/qlearning/b/build.yaml b/tests/fixtures/qlearning/b/build.yaml new file mode 100644 index 00000000..90d59320 --- /dev/null +++ b/tests/fixtures/qlearning/b/build.yaml @@ -0,0 +1,5 @@ +image: "alpine" +prelude: + - echo b > /bprelude +steps: + - echo b > /b \ No newline at end of file diff --git a/tests/fixtures/qlearning/b/definition.yaml b/tests/fixtures/qlearning/b/definition.yaml new file mode 100644 index 00000000..f3c125d9 --- /dev/null +++ b/tests/fixtures/qlearning/b/definition.yaml @@ -0,0 +1,7 @@ +category: "test" +name: "b" +version: "1.0" +conflicts: + - category: "test" + name: "c" + version: "1.0" \ No newline at end of file diff --git a/tests/fixtures/qlearning/c/build.yaml b/tests/fixtures/qlearning/c/build.yaml new file mode 100644 index 00000000..4ab80d6f --- /dev/null +++ b/tests/fixtures/qlearning/c/build.yaml @@ -0,0 +1,5 @@ +image: "alpine" +prelude: + - echo c > /cprelude +steps: + - echo c > /c \ No newline at end of file diff --git a/tests/fixtures/qlearning/c/definition.yaml b/tests/fixtures/qlearning/c/definition.yaml new file mode 100644 index 00000000..2b2a635f --- /dev/null +++ b/tests/fixtures/qlearning/c/definition.yaml @@ -0,0 +1,3 @@ +category: "test" +name: "c" +version: "1.0" \ No newline at end of file diff --git a/tests/fixtures/qlearning/d/build.yaml b/tests/fixtures/qlearning/d/build.yaml new file mode 100644 index 00000000..bf3ca25e --- /dev/null +++ b/tests/fixtures/qlearning/d/build.yaml @@ -0,0 +1,5 @@ +image: "alpine" +prelude: + - echo d > /dprelude +steps: + - echo d > /d \ No newline at end of file diff --git a/tests/fixtures/qlearning/d/definition.yaml b/tests/fixtures/qlearning/d/definition.yaml new file mode 100644 index 00000000..4e89ccb6 --- /dev/null +++ b/tests/fixtures/qlearning/d/definition.yaml @@ -0,0 +1,3 @@ +category: "test" +name: "d" +version: "1.0" \ No newline at end of file diff --git a/tests/fixtures/qlearning/e/build.yaml b/tests/fixtures/qlearning/e/build.yaml new file mode 100644 index 00000000..797980c0 --- /dev/null +++ b/tests/fixtures/qlearning/e/build.yaml @@ -0,0 +1,9 @@ +prelude: + - echo e > /eprelude +steps: + - echo e > /e +requires: +- category: "test" + name: "b" + version: "1.0" + diff --git a/tests/fixtures/qlearning/e/definition.yaml b/tests/fixtures/qlearning/e/definition.yaml new file mode 100644 index 00000000..31e58090 --- /dev/null +++ b/tests/fixtures/qlearning/e/definition.yaml @@ -0,0 +1,8 @@ +category: "test" +name: "e" +version: "1.0" +requires: +- category: "test" + name: "b" + version: "1.0" + diff --git a/tests/fixtures/qlearning/f/build.yaml b/tests/fixtures/qlearning/f/build.yaml new file mode 100644 index 00000000..e2f6d4a2 --- /dev/null +++ b/tests/fixtures/qlearning/f/build.yaml @@ -0,0 +1,5 @@ +prelude: + - echo f > /eprelude +steps: + - echo f > /f +image: "alpine" diff --git a/tests/fixtures/qlearning/f/definition.yaml b/tests/fixtures/qlearning/f/definition.yaml new file mode 100644 index 00000000..6a150c71 --- /dev/null +++ b/tests/fixtures/qlearning/f/definition.yaml @@ -0,0 +1,3 @@ +category: "test" +name: "f" +version: "1.0" \ No newline at end of file diff --git a/tests/integration/03_qlearning.sh b/tests/integration/03_qlearning.sh new file mode 100755 index 00000000..0cad6b73 --- /dev/null +++ b/tests/integration/03_qlearning.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +export LUET_NOLOCK=true + +oneTimeSetUp() { +export tmpdir="$(mktemp -d)" +} + +oneTimeTearDown() { + rm -rf "$tmpdir" +} + +testBuild() { + mkdir $tmpdir/testbuild + luet build --all --tree "$ROOT_DIR/tests/fixtures/qlearning" --destination $tmpdir/testbuild --compression gzip + buildst=$? + assertEquals 'builds successfully' "$buildst" "0" + assertTrue 'create package dep B' "[ -e '$tmpdir/testbuild/b-test-1.0.package.tar.gz' ]" + assertTrue 'create package' "[ -e '$tmpdir/testbuild/c-test-1.0.package.tar.gz' ]" +} + +testRepo() { + assertTrue 'no repository' "[ ! -e '$tmpdir/testbuild/repository.yaml' ]" + luet create-repo --tree "$ROOT_DIR/tests/fixtures/qlearning" \ + --output $tmpdir/testbuild \ + --packages $tmpdir/testbuild \ + --name "test" \ + --descr "Test Repo" \ + --urls $tmpdir/testrootfs \ + --type disk > /dev/null + + createst=$? + assertEquals 'create repo successfully' "$createst" "0" + assertTrue 'create repository' "[ -e '$tmpdir/testbuild/repository.yaml' ]" +} + +testConfig() { + mkdir $tmpdir/testrootfs + cat < $tmpdir/luet.yaml +general: + debug: true +system: + rootfs: $tmpdir/testrootfs + database_path: "/" + database_engine: "boltdb" +repositories: + - name: "main" + type: "disk" + enable: true + urls: + - "$tmpdir/testbuild" +EOF + luet config --config $tmpdir/luet.yaml + res=$? + assertEquals 'config test successfully' "$res" "0" +} + +testInstall() { + luet install --config $tmpdir/luet.yaml test/c + #luet install --config $tmpdir/luet.yaml test/c-1.0 > /dev/null + installst=$? + assertEquals 'install test successfully' "$installst" "0" + assertTrue 'package C installed' "[ -e '$tmpdir/testrootfs/c' ]" +} + +testFullInstall() { + output=$(luet install --config $tmpdir/luet.yaml test/d test/f test/e test/a) + installst=$? + assertEquals 'cannot install' "$installst" "1" + assertTrue 'package D installed' "[ ! -e '$tmpdir/testrootfs/d' ]" + assertTrue 'package F installed' "[ ! -e '$tmpdir/testrootfs/f' ]" +} + +testInstallAgain() { + output=$(luet install --solver-type qlearning --config $tmpdir/luet.yaml test/d test/f test/e test/a) + installst=$? + assertEquals 'install test successfully' "$installst" "0" + assertNotContains 'contains warning' "$output" 'Filtering out' + assertTrue 'package D installed' "[ -e '$tmpdir/testrootfs/d' ]" + assertTrue 'package F installed' "[ -e '$tmpdir/testrootfs/f' ]" + assertTrue 'package E not installed' "[ ! -e '$tmpdir/testrootfs/e' ]" + assertTrue 'package A not installed' "[ ! -e '$tmpdir/testrootfs/a' ]" +} + +testCleanup() { + luet cleanup --config $tmpdir/luet.yaml + installst=$? + assertEquals 'install test successfully' "$installst" "0" + assertTrue 'package installed' "[ ! -e '$tmpdir/testrootfs/packages/c-test-1.0.package.tar.gz' ]" +} + +# Load shUnit2. +. "$ROOT_DIR/tests/integration/shunit2"/shunit2 + diff --git a/tests/integration/run.sh b/tests/integration/run.sh index 83aa532f..3b1c9060 100755 --- a/tests/integration/run.sh +++ b/tests/integration/run.sh @@ -11,7 +11,7 @@ popd export PATH=$ROOT_DIR/tests/integration/bin/:$PATH -"$ROOT_DIR/tests/integration/01_simple.sh" -"$ROOT_DIR/tests/integration/01_simple_gzip.sh" -"$ROOT_DIR/tests/integration/02_create_repo_from_config.sh" - +for script in $(ls "$ROOT_DIR/tests/integration/" | grep '^[0-9]*_.*.sh'); do + echo "Executing script '$script'." + $ROOT_DIR/tests/integration/$script +done \ No newline at end of file From d6cdb8ea42e802908bedaf40eb4b51ee5de2c580 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 14:33:59 +0100 Subject: [PATCH 29/30] Update README --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index aa8b9693..c5afad7b 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,23 @@ To install luet, you can grab a release on the [Release page](https://github.com Luet is not feature-complete yet, it can build, install/uninstall/upgrade packages - but it doesn't support yet all the features you would normally expect from a Package Manager nowadays. +# Dependency solving + +Luet uses SAT and Reinforcement learning engine for dependency solving. +It encodes the package requirements into a SAT problem, using gophersat to solve the dependency tree and give a concrete model as result. + +## SAT encoding + +Each package and its constraints are encoded and built around [OPIUM](https://ranjitjhala.github.io/static/opium.pdf). Additionally, Luet treats +also selectors seamlessly while building the model, adding *ALO* ( *At least one* ) and *AMO* ( *At most one* ) rules to guarantee coherence within the installed system. + +## Reinforcement learning + +Luet also implements a small and portable qlearning agent that will try to solve conflict on your behalf +when they arises while trying to validate your queries against the system model. + +To leverage it, simply pass ```--solver-type qlearning``` to the subcommands that supports it ( you can check out by invoking ```--help``` ). + ## Documentation [Documentation](https://luet-lab.github.io/docs) is available, or From 3563636ea9215a6473a88a9eebf859134c52b892 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 12 Feb 2020 15:16:33 +0100 Subject: [PATCH 30/30] Update config example in contrib/config/luet.yaml --- contrib/config/luet.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/contrib/config/luet.yaml b/contrib/config/luet.yaml index 9738e725..954cbf35 100644 --- a/contrib/config/luet.yaml +++ b/contrib/config/luet.yaml @@ -108,3 +108,21 @@ # basic: "mybasicauth" # Define token authentication header # token: "mytoken" +# --------------------------------------------- +# Solver parameter configuration: +# --------------------------------------------- +# solver: +# +# Solver strategy to solve possible conflicts during depedency +# solving. Defaults to empty (none). Available: qlearning +# type: "" +# +# Solver agent learning rate. 0.1 to 1.0 +# rate: 0.7 +# +# Learning discount factor. +# discount: 1.0 +# +# Number of overall attempts that the solver has available before bailing out. +# max_attempts: 9000 +# \ No newline at end of file