2020-02-10 08:41:09 +00:00
|
|
|
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
|
|
|
|
//
|
|
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package solver
|
|
|
|
|
|
|
|
import (
|
2020-02-10 16:16:35 +00:00
|
|
|
"fmt"
|
2020-02-11 08:51:52 +00:00
|
|
|
"strconv"
|
2020-02-10 16:16:35 +00:00
|
|
|
|
|
|
|
"github.com/ecooper/qlearning"
|
2020-02-11 08:51:52 +00:00
|
|
|
"github.com/mudler/gophersat/bf"
|
2020-02-10 16:16:35 +00:00
|
|
|
pkg "github.com/mudler/luet/pkg/package"
|
2020-02-10 08:41:09 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2020-02-11 08:51:52 +00:00
|
|
|
type ActionType int
|
|
|
|
|
2020-02-10 16:16:35 +00:00
|
|
|
const (
|
2020-02-11 08:51:52 +00:00
|
|
|
Solved = 1
|
|
|
|
NoSolution = iota
|
|
|
|
Going = iota
|
|
|
|
ActionRemoved = iota
|
|
|
|
ActionAdded = iota
|
2020-02-10 16:16:35 +00:00
|
|
|
)
|
|
|
|
|
2020-02-10 08:41:09 +00:00
|
|
|
//. "github.com/mudler/luet/pkg/logger"
|
|
|
|
|
|
|
|
// PackageResolver assists PackageSolver on unsat cases
|
|
|
|
type PackageResolver interface {
|
2020-02-10 16:16:35 +00:00
|
|
|
Solve(bf.Formula, PackageSolver) (PackagesAssertions, error)
|
2020-02-10 08:41:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type DummyPackageResolver struct {
|
|
|
|
}
|
|
|
|
|
2020-02-10 16:16:35 +00:00
|
|
|
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
|
2020-02-11 08:51:52 +00:00
|
|
|
Correct []Choice
|
2020-02-10 16:16:35 +00:00
|
|
|
|
|
|
|
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))
|
|
|
|
|
2020-02-11 08:51:52 +00:00
|
|
|
resolver.Correct = make([]Choice, len(resolver.Targets), len(resolver.Targets))
|
2020-02-10 16:16:35 +00:00
|
|
|
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())
|
2020-02-11 08:51:52 +00:00
|
|
|
resolver.ToAttempt = 0 // We won. As we had one sat, let's take it
|
2020-02-10 16:16:35 +00:00
|
|
|
} 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 {
|
2020-02-11 08:51:52 +00:00
|
|
|
if pack.String() == w.String() {
|
2020-02-10 16:16:35 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-02-11 08:51:52 +00:00
|
|
|
func (resolver *QLearningResolver) Try(c Choice) error {
|
|
|
|
pack := c.String()
|
|
|
|
resolver.Attempted[pack+strconv.Itoa(int(c.Action))] = true // increase the count
|
2020-02-10 16:16:35 +00:00
|
|
|
s, _ := resolver.Solver.(*Solver)
|
|
|
|
var filtered []pkg.Package
|
|
|
|
|
2020-02-11 08:51:52 +00:00
|
|
|
switch c.Action {
|
|
|
|
case ActionAdded:
|
|
|
|
for _, p := range resolver.Targets {
|
|
|
|
if p.String() == pack {
|
|
|
|
resolver.Solver.(*Solver).Wanted = append(resolver.Solver.(*Solver).Wanted, p)
|
|
|
|
}
|
2020-02-10 16:16:35 +00:00
|
|
|
}
|
|
|
|
|
2020-02-11 08:51:52 +00:00
|
|
|
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")
|
|
|
|
|
|
|
|
}
|
2020-02-10 16:16:35 +00:00
|
|
|
|
|
|
|
_, err := resolver.Solver.Solve()
|
|
|
|
|
2020-02-11 08:51:52 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Choose applies a pack attempt, returning
|
|
|
|
// true if the formula returns sat.
|
|
|
|
//
|
|
|
|
// Choose updates the resolver's state.
|
|
|
|
func (resolver *QLearningResolver) Choose(c Choice) bool {
|
|
|
|
err := resolver.Try(c)
|
2020-02-10 16:16:35 +00:00
|
|
|
|
|
|
|
if err == nil {
|
2020-02-11 08:51:52 +00:00
|
|
|
resolver.Correct = append(resolver.Correct, c)
|
|
|
|
// resolver.Correct[index] = pack
|
2020-02-10 16:16:35 +00:00
|
|
|
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 {
|
2020-02-11 08:51:52 +00:00
|
|
|
actions := make([]qlearning.Action, 0, (len(resolver.Targets)-1)*2)
|
2020-02-10 16:16:35 +00:00
|
|
|
|
2020-02-11 08:51:52 +00:00
|
|
|
fmt.Println("Actions")
|
2020-02-10 16:16:35 +00:00
|
|
|
for _, pack := range resolver.Targets {
|
2020-02-11 08:21:25 +00:00
|
|
|
// attempted := resolver.Attempted[pack.String()]
|
|
|
|
// if !attempted {
|
2020-02-11 08:51:52 +00:00
|
|
|
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")
|
2020-02-11 08:21:25 +00:00
|
|
|
// }
|
2020-02-10 16:16:35 +00:00
|
|
|
}
|
|
|
|
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 {
|
2020-02-11 08:51:52 +00:00
|
|
|
return fmt.Sprintf("%v", resolver.Correct)
|
2020-02-10 16:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Choice implements qlearning.Action for a package choice for removal from wanted targets
|
|
|
|
type Choice struct {
|
|
|
|
Package string
|
2020-02-11 08:51:52 +00:00
|
|
|
Action ActionType
|
2020-02-10 16:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
2020-02-11 08:51:52 +00:00
|
|
|
resolver.Choose(*choice)
|
2020-02-10 16:16:35 +00:00
|
|
|
|
|
|
|
return resolver
|
2020-02-10 08:41:09 +00:00
|
|
|
}
|