mirror of
https://github.com/newnius/YAO-scheduler.git
synced 2025-06-08 23:01:55 +00:00
334 lines
7.6 KiB
Go
334 lines
7.6 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"math/rand"
|
||
|
"github.com/MaxHalford/eaopt"
|
||
|
"time"
|
||
|
"strconv"
|
||
|
"math"
|
||
|
)
|
||
|
|
||
|
var nodesMap map[string]Node
|
||
|
var tasksMap map[string]Task
|
||
|
|
||
|
type Node struct {
|
||
|
ClientID string `json:"id"`
|
||
|
Domain int `json:"domain"`
|
||
|
Rack int `json:"rack"`
|
||
|
}
|
||
|
|
||
|
type Task struct {
|
||
|
Name string `json:"name"`
|
||
|
IsPS bool `json:"is_ps"`
|
||
|
}
|
||
|
|
||
|
// An valid allocation
|
||
|
type Allocation struct {
|
||
|
TasksOnNode map[string][]Task // tasks on nodes[id]
|
||
|
Nodes map[string]Node
|
||
|
Valid map[string]bool
|
||
|
}
|
||
|
|
||
|
// Evaluate a Vector with the Drop-Wave function which takes two variables as
|
||
|
// input and reaches a minimum of -1 in (0, 0). The function is simple so there
|
||
|
// isn't any error handling to do.
|
||
|
func (X Allocation) Evaluate() (float64, error) {
|
||
|
if !X.Valid["flag"] {
|
||
|
fmt.Println("Invalid allocation")
|
||
|
return math.MaxFloat64, nil
|
||
|
}
|
||
|
cost := 0
|
||
|
taskToNode := map[string]string{}
|
||
|
for id, tasks := range X.TasksOnNode {
|
||
|
for _, task := range tasks {
|
||
|
taskToNode[task.Name] = id
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for taskI, nodeI := range taskToNode {
|
||
|
for taskJ, nodeJ := range taskToNode {
|
||
|
if taskI == taskJ {
|
||
|
continue
|
||
|
}
|
||
|
if tasksMap[taskI].IsPS == tasksMap[taskJ].IsPS {
|
||
|
continue
|
||
|
}
|
||
|
if X.Nodes[nodeI].Domain != X.Nodes[nodeJ].Domain {
|
||
|
cost += 4
|
||
|
} else if X.Nodes[nodeI].Rack != X.Nodes[nodeJ].Rack {
|
||
|
cost += 2
|
||
|
} else if nodeI != nodeJ {
|
||
|
cost += 1
|
||
|
} else {
|
||
|
cost += 0
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//fmt.Println(taskToNode, cost/2, len(X.Nodes))
|
||
|
return float64(cost / 2), nil
|
||
|
}
|
||
|
|
||
|
// Mutate a Vector by resampling each element from a normal distribution with
|
||
|
// probability 0.8.
|
||
|
func (X Allocation) Mutate(rng *rand.Rand) {
|
||
|
if !X.Valid["flag"] {
|
||
|
fmt.Println("Invalid allocation")
|
||
|
return
|
||
|
}
|
||
|
//fmt.Println("Mutate")
|
||
|
fmt.Println("Before", X)
|
||
|
|
||
|
/* decrease node */
|
||
|
var nodeIDs []string
|
||
|
for nodeID := range X.Nodes {
|
||
|
nodeIDs = append(nodeIDs, nodeID)
|
||
|
}
|
||
|
randIndex := rng.Intn(len(X.Nodes))
|
||
|
nodeID := nodeIDs[randIndex]
|
||
|
|
||
|
/* reschedule tasks on tgt node */
|
||
|
var tasks []Task
|
||
|
if _, ok := X.TasksOnNode[nodeID]; ok {
|
||
|
for _, task := range X.TasksOnNode[nodeID] {
|
||
|
tasks = append(tasks, task)
|
||
|
}
|
||
|
delete(X.TasksOnNode, nodeID)
|
||
|
}
|
||
|
delete(X.Nodes, nodeID)
|
||
|
|
||
|
fmt.Println(tasks)
|
||
|
|
||
|
/* first-fit */
|
||
|
for _, task := range tasks {
|
||
|
flag := false
|
||
|
for nodeID3 := range X.Nodes {
|
||
|
if len(X.TasksOnNode[nodeID3]) < 3 {
|
||
|
X.TasksOnNode[nodeID3] = append(X.TasksOnNode[nodeID3], task)
|
||
|
flag = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if !flag {
|
||
|
X.Valid["flag"] = false
|
||
|
}
|
||
|
}
|
||
|
fmt.Println("After", X)
|
||
|
|
||
|
/* exchange tasks */
|
||
|
//randIndexM := rng.Intn(len(X))
|
||
|
//randIndexN := rng.Intn(len(X))
|
||
|
//X[randIndexM].Tasks, X[randIndexN].Tasks = X[randIndexN].Tasks, X[randIndexM].Tasks
|
||
|
}
|
||
|
|
||
|
// Crossover a Vector with another Vector by applying uniform crossover.
|
||
|
func (X Allocation) Crossover(Y eaopt.Genome, rng *rand.Rand) {
|
||
|
//fmt.Println("Crossover")
|
||
|
if !Y.(Allocation).Valid["flag"] || !X.Valid["flag"] {
|
||
|
return
|
||
|
}
|
||
|
taskToNode := map[string]string{}
|
||
|
for nodeID, tasks := range X.TasksOnNode {
|
||
|
for _, task := range tasks {
|
||
|
taskToNode[task.Name] = nodeID
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var nodeIDs []string
|
||
|
for nodeID := range Y.(Allocation).Nodes {
|
||
|
nodeIDs = append(nodeIDs, nodeID)
|
||
|
}
|
||
|
|
||
|
//fmt.Println(nodeIDs, Y.(Allocation))
|
||
|
randIndex := rng.Intn(len(nodeIDs))
|
||
|
nodeID := nodeIDs[randIndex]
|
||
|
|
||
|
for _, task := range Y.(Allocation).TasksOnNode[nodeID] {
|
||
|
//fmt.Println(Y.(Allocation).TasksOnNode[nodeID])
|
||
|
idx := -1
|
||
|
nodeID2, ok := taskToNode[task.Name]
|
||
|
if !ok {
|
||
|
fmt.Println("Error", taskToNode, X.TasksOnNode, task.Name)
|
||
|
}
|
||
|
for i, task2 := range X.TasksOnNode[nodeID2] {
|
||
|
if task2.Name == task.Name {
|
||
|
idx = i
|
||
|
}
|
||
|
}
|
||
|
if idx == -1 {
|
||
|
fmt.Println("Error 2", taskToNode, X.TasksOnNode, task.Name)
|
||
|
}
|
||
|
//fmt.Println(X.TasksOnNode)
|
||
|
copy(X.TasksOnNode[nodeID2][idx:], X.TasksOnNode[nodeID2][idx+1:])
|
||
|
X.TasksOnNode[nodeID2] = X.TasksOnNode[nodeID2][:len(X.TasksOnNode[nodeID2])-1]
|
||
|
//fmt.Println(X.TasksOnNode)
|
||
|
}
|
||
|
/* reschedule tasks on tgt node */
|
||
|
var tasks []Task
|
||
|
if _, ok := X.TasksOnNode[nodeID]; ok {
|
||
|
for _, task := range X.TasksOnNode[nodeID] {
|
||
|
tasks = append(tasks, task)
|
||
|
}
|
||
|
delete(X.TasksOnNode, nodeID)
|
||
|
}
|
||
|
|
||
|
if _, ok := X.Nodes[nodeID]; ok {
|
||
|
delete(X.Nodes, nodeID)
|
||
|
}
|
||
|
X.Nodes[nodeID] = Y.(Allocation).Nodes[nodeID]
|
||
|
|
||
|
var newTasksOnNode []Task
|
||
|
for _, task := range Y.(Allocation).TasksOnNode[nodeID] {
|
||
|
newTasksOnNode = append(newTasksOnNode, task)
|
||
|
}
|
||
|
X.TasksOnNode[nodeID] = newTasksOnNode
|
||
|
|
||
|
/* first-fit */
|
||
|
for _, task := range tasks {
|
||
|
flag := false
|
||
|
for nodeID3 := range X.Nodes {
|
||
|
if len(X.TasksOnNode[nodeID3]) < 3 {
|
||
|
X.TasksOnNode[nodeID3] = append(X.TasksOnNode[nodeID3], task)
|
||
|
flag = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if !flag {
|
||
|
X.Valid["flag"] = false
|
||
|
}
|
||
|
}
|
||
|
//fmt.Println()
|
||
|
//fmt.Println("crossover", X.TasksOnNode)
|
||
|
}
|
||
|
|
||
|
// Clone a Vector to produce a new one that points to a different slice.
|
||
|
func (X Allocation) Clone() eaopt.Genome {
|
||
|
if !X.Valid["flag"] {
|
||
|
//fmt.Println(X.Valid)
|
||
|
}
|
||
|
Y := Allocation{TasksOnNode: map[string][]Task{}, Nodes: map[string]Node{}, Valid: map[string]bool{"flag": X.Valid["flag"]}}
|
||
|
for id, node := range X.Nodes {
|
||
|
Y.Nodes[id] = node
|
||
|
}
|
||
|
for id, tasks := range X.TasksOnNode {
|
||
|
var t []Task
|
||
|
for _, task := range tasks {
|
||
|
t = append(t, task)
|
||
|
}
|
||
|
Y.TasksOnNode[id] = t
|
||
|
}
|
||
|
return Y
|
||
|
}
|
||
|
|
||
|
// VectorFactory returns a random vector by generating 2 values uniformally
|
||
|
// distributed between -10 and 10.
|
||
|
func VectorFactory(rng *rand.Rand) eaopt.Genome {
|
||
|
allocation := Allocation{TasksOnNode: map[string][]Task{}, Nodes: map[string]Node{}, Valid: map[string]bool{"flag": true}}
|
||
|
|
||
|
var nodes []Node
|
||
|
var tasks []Task
|
||
|
|
||
|
for _, node := range nodesMap {
|
||
|
nodes = append(nodes, node)
|
||
|
}
|
||
|
for _, task := range tasksMap {
|
||
|
tasks = append(tasks, task)
|
||
|
}
|
||
|
|
||
|
/* shuffle */
|
||
|
for n := len(nodes); n > 0; n-- {
|
||
|
randIndex := rng.Intn(n)
|
||
|
nodes[n-1], nodes[randIndex] = nodes[randIndex], nodes[n-1]
|
||
|
}
|
||
|
for n := len(tasks); n > 0; n-- {
|
||
|
randIndex := rng.Intn(n)
|
||
|
tasks[n-1], tasks[randIndex] = tasks[randIndex], tasks[n-1]
|
||
|
}
|
||
|
|
||
|
/* pick nodes */
|
||
|
for _, node := range nodesMap {
|
||
|
allocation.Nodes[node.ClientID] = node
|
||
|
}
|
||
|
|
||
|
/* first-fit */
|
||
|
for _, task := range tasks {
|
||
|
flag := false
|
||
|
for id, _ := range allocation.Nodes {
|
||
|
if len(allocation.TasksOnNode[id]) < 3 {
|
||
|
allocation.TasksOnNode[id] = append(allocation.TasksOnNode[id], task)
|
||
|
flag = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if !flag {
|
||
|
allocation.Valid["flag"] = false
|
||
|
}
|
||
|
}
|
||
|
//fmt.Println(allocation)
|
||
|
return allocation
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
numTask := 100
|
||
|
|
||
|
nodesMap = map[string]Node{}
|
||
|
tasksMap = map[string]Task{}
|
||
|
|
||
|
for i := 0; i < numTask*3; i++ {
|
||
|
nodesMap[strconv.Itoa(i)] = Node{ClientID: strconv.Itoa(i), Rack: i % 2, Domain: i % 1}
|
||
|
}
|
||
|
for i := 0; i < numTask; i++ {
|
||
|
isPS := false
|
||
|
if i%5 == 0 {
|
||
|
isPS = true
|
||
|
}
|
||
|
tasksMap[strconv.Itoa(i)] = Task{Name: strconv.Itoa(i), IsPS: isPS}
|
||
|
}
|
||
|
|
||
|
// Instantiate a GA with a GAConfig
|
||
|
var ga, err = eaopt.NewDefaultGAConfig().NewGA()
|
||
|
if err != nil {
|
||
|
fmt.Println(err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Set the number of generations to run for
|
||
|
ga.NGenerations = 4000
|
||
|
ga.NPops = 1
|
||
|
ga.PopSize = 20
|
||
|
|
||
|
// Add a custom print function to track progress
|
||
|
ga.Callback = func(ga *eaopt.GA) {
|
||
|
fmt.Printf("Best fitness at generation %d: %f\n", ga.Generations, ga.HallOfFame[0].Fitness)
|
||
|
}
|
||
|
|
||
|
bestFitness := -1.0
|
||
|
count := 0
|
||
|
|
||
|
ga.EarlyStop = func(ga *eaopt.GA) bool {
|
||
|
gap := math.Abs(ga.HallOfFame[0].Fitness - bestFitness)
|
||
|
if gap <= 0.000001 {
|
||
|
if count >= 20 {
|
||
|
fmt.Println("Early Stop")
|
||
|
return true
|
||
|
} else {
|
||
|
count++
|
||
|
}
|
||
|
} else {
|
||
|
bestFitness = ga.HallOfFame[0].Fitness
|
||
|
count = 1
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Find the minimum
|
||
|
ts := time.Now()
|
||
|
err = ga.Minimize(VectorFactory)
|
||
|
fmt.Println(time.Since(ts))
|
||
|
if err != nil {
|
||
|
fmt.Println(err)
|
||
|
return
|
||
|
}
|
||
|
}
|