Skip to content
Snippets Groups Projects
Commit 69b584af authored by Maxime MORGE's avatar Maxime MORGE
Browse files

Initial commit

parent ab46a8d4
No related branches found
No related tags found
No related merge requests found
Showing
with 1716 additions and 1 deletion
### OSX ###
*.DS_Store
### SBT ###
target/
lib_managed/
src_managed/
project/boot/
.history
.cache
# sbt specific
.lib/
dist/*
project/plugins/project/
### Scala ###
*.class
*.log
### Emacs ###
\#*\#
### Intellij ###
.idea/
*.iml
.cache-main
### VS ###
.metals/
.vscode/
.bloop
project/.bloop
## Misc ###
project/build.properties
project/project/
experiments/figures/
LICENSE 0 → 100644
This diff is collapsed.
# ScaDCOP
ScaDCOP is a Scala library of algorithm for Distributed Constraints Optimization Problems
ScaDOPT is a Scala library of algorithm for Distributed Constraints Optimization Problems
\ No newline at end of file
## What is ScaDCOP ?
ScaDCOP is a Scala library of algorithm for Distributed Constraints
Optimization Problems. We have implemented our prototype with the
[Scala](https://www.scala-lang.org/) programming language and the
[Akka](http://akka.io/) toolkit. The latter, which is based on the
actor model, allows us to fill the gap between the specification and
its implementation.
## Requirements
In order to run the demonstration you need:
- the Java virtual machine [JVM 1.8.0_60](http://www.oracle.com/technetwork/java/javase/downloads/index.html).
In order to compile the code you need:
- the programming language [Scala 2.12.4](http://www.scala-lang.org/download/);
- the interactive build tool [SBT 1.2.1](http://www.scala-sbt.org/download.html).
## Usage
java -jar scadcop-assembly-X.Y.jar
## Installation
Compile
sbt compile
then
sbt "run org.ALGO.util.Main"
and eventually
sbt assembly
## Contributors
Copyright (C) Maxime MORGE 2020
## License
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 3 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/>.
name := "ScaDCOP"
version := "0.1"
mainClass in (Compile,run) := Some("org.scadcop.util.Main")
mainClass in assembly := Some("org.scadcop.util.Main")
trapExit := false
resolvers += "Artifactory-UCL" at "http://artifactory.info.ucl.ac.be/artifactory/libs-snapshot-local/"
resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
scalaVersion := "2.12.4"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.5.20",
"com.typesafe.akka" %% "akka-remote" % "2.5.20"
)
// Copyright (C) Maxime MORGE 2020
package org.scadcop.actor
import akka.actor.ActorRef
import org.scadcop.problem.Variable
/**
* Class representing an index of the names and addresses of peers
*/
class Directory {
var adr : Map[Variable, ActorRef] = Map[Variable, ActorRef]()//Variables' actors
var variables : Map[ActorRef, Variable] = Map[ActorRef, Variable]()// Actors' variable
/**
* String representation
*/
override def toString: String = allVariables().mkString("[",", ","]")
/**
* Add to the directory
* @param variable to add
* @param ref to add
*/
def add(variable: Variable, ref: ActorRef) : Unit = {
if ( ! adr.keySet.contains(variable) && ! variables.keySet.contains(ref)) {
adr += (variable -> ref)
variables += (ref -> variable)
}
else throw new RuntimeException(s"$variable and/or $ref already in the directory")
}
def allActors() : Iterable[ActorRef] = adr.values
def allVariables() : Iterable[Variable] = variables.values
def peers(variable: Variable) : Set[Variable] = allVariables().filterNot(_ ==variable).toSet
def peersActor(variable: Variable) : Iterable[ActorRef] = peers(variable: Variable).map(w => adr(w))
}
// Copyright (C) Maxime MORGE 2019
package org.scadcop.actor
import org.scadcop.problem.{Context, Variable, Value}
/**
* All possible messages between the actors
*/
abstract class Message
// Managing messages
// Debuging message
case object Trace extends Message
// Starts the solving
case object Start extends Message
// Initiates a variable agent with a directory, children, and eventually a parent
case class Init(directory : Directory, parent : Option[Variable], children: Set[Variable]) extends Message
// The variable agent is ready to start
case object Ready extends Message
// Gives an assignment and the statistics
case class Outcome(assignment : Context) extends Message
// Stops an agent
case object Stop extends Message
// DCOP messages
// Fixes the value of the variable agent
case class Assign(value : Value) extends Message
// Generalizes the nogood message of DisCSP
case class Cost(lb : Double, ub: Double, ctxt : Context) extends Message
// Reduces the redundant search
case class Threshold(threshold: Double, ctxt : Context) extends Message
// Terminates the children with a context
case class Terminate(ctxt: Context) extends Message
\ No newline at end of file
// Copyright (C) Maxime MORGE 2019
package org.scadcop.actor
import org.scadcop.problem.{Context, DCOP, Variable}
import org.scadcop.dfs.DFS
import akka.actor.{Actor, ActorRef, Props}
/**
* SolverAgent which starts and stops the computation of an assignment
* @param pb DCOP instance
* */
class SolverAgent(val pb: DCOP) extends Actor{
var debug = true
// The actor which triggers the simulation and gathers the steps
private var solver : ActorRef= context.parent
// Number of agents which are ready
private var nbReady = 0
// Is the solver agent started
var started = false
// White page variable/actor
private val directory = new Directory()
// The assignment to build
private val assignment = new Context(pb)
// Deploy and init
pb.variables.foreach{ variable =>
val actor = context.actorOf(Props(classOf[VariableAgent], variable, pb), variable.id.toString)
directory.add(variable, actor) // Add it to the directory
}
init(DFS(pb), None)
/**
* Returns the root variable of the DFS whose children have been initiated
*/
def init(dfs: DFS, parent: Option[Variable]) : Variable = {
var neighbors = Set[Variable]()
dfs.children.foreach{ subDfs =>
neighbors += init(subDfs,Some(dfs.root))
}
directory.adr(dfs.root) ! Init(directory, parent, neighbors)
dfs.root
}
/**
* Message handling
*/
override def receive: Receive = {
// When a variable agent is ready
case Ready =>
nbReady += 1
if (nbReady == pb.variables.size && started) directory.allActors().foreach(_ ! Start)
//When debugging mode is triggered
case Trace =>
debug = true
directory.allActors().foreach(_ ! Trace)
//When the solving is triggered
case Start =>
solver = sender
started = true
if (nbReady == pb.variables.size ) directory.allActors().foreach(_ ! Start)
//When the value of variable is setup
case Assign(value) =>
assignment.valuation += (directory.variables(sender) -> value)
if (assignment.isAssignment){
solver ! Outcome(assignment) // reports the allocation
context.stop(self) //stops the solverAgent
}
// Unexpected message
case msg@_ =>
println("WARNING: SolverAgent receives a message which was not expected: " + msg)
}
}
// Copyright (C) Maxime MORGE 2019
package org.scadcop.actor
import org.scadcop.problem.{Context, DCOP, Value, Variable}
import org.scadcop.util.MathUtils.MathUtils
import akka.actor.{Actor, ActorRef}
/**
* SolverAgent which starts and stops the computation of an allocation
* @param variable which should be valuated
* @param pb DCOP instance
* */
class VariableAgent(variable : Variable, pb : DCOP) extends Actor {
var debug = true
var trace = true
// The actor which triggers the resolution
private var solverAgent : ActorRef= context.parent
// White page variable/actor
private var directory = new Directory()
// parent variable in the DFS
private var parent : Option[Variable] = None
// Children variables in the DFS
private var children = Set[Variable]()
// Agent's view of the assignment of higher neighbors
private var currentContext : Context = new Context(pb)
// lower bound for the different couples (value,child)
private var lb = Map[(Value,Variable),Double]()
// upper bound for the different couples (value,child)
private var ub = Map[(Value,Variable),Double]()
// The backtrack threshold for the different couples (value,child)
private var t = Map[(Value,Variable),Double]()
private var threshold : Double = 0.0
// Current value of the variable
private var di : Value = variable.domain.head
// True if the agent has received a terminate message from the parent
private var terminated : Boolean = false
/**
* Returns true if the agent is a leaf agent
*/
def isLeafAgent: Boolean = children.isEmpty
/**
* Returns true if the agent is a root agent
*/
def isRootAgent: Boolean = parent.isEmpty
/**
* Returns the lower level neighbors
*/
def neighbors() : Set[ActorRef] = children.map(v => directory.adr(v))
/**
* Returns the value which minimizes a bound
*/
def minimize(bound : Value=> Double) : Value = {
var dj = variable.domain.head
var min = Double.MaxValue
variable.domain.foreach { d =>
if (bound(d) < min) {
dj = d
min = bound(d)
}
}
dj
}
/**
* Resets bounds
*/
def resetBound() : Unit ={
//If the agent is not a leaf but has not yet received any COST messages from its children,
// UB is equal to maximum value Inf and LB is equal to the minimum local cost δ(d) over all value choices d ∈ Di .
variable.domain.foreach{ v: Value =>
children.foreach{ child : Variable =>
lb += ((v, child) -> 0.0)
ub += ((v, child) -> Double.MaxValue)
t += ((v, child) -> 0.0)
}
}
}
/**
* Returns the local cost for a specific value
*/
def (value: Value) : Double = {
var cost = 0.0
currentContext.valuation.keys.filter(_ == variable).foreach { otherVariable =>
pb.constraints.foreach { c =>
if (c.isOver(variable) && c.isOver(otherVariable)) {
val cc = c.cost(variable, currentContext.valuation(variable), otherVariable, currentContext.valuation(otherVariable))
cost += cc
}
}
}
cost
}
/**
* Returns the lower bound for the subtree rooted at the variable
* when the variable chooses value
*/
def LB(value: Value) : Double = {
∂(value) + (if (! isLeafAgent) children.toSeq.map(v => lb(value,v) ).sum else 0.0)
}
/**
* Returns the upper bound for the subtree rooted at the variable
* when the variable chooses value
*/
def UB(value: Value) : Double = {
∂(value) + (if (! isLeafAgent) children.toSeq.map(v => ub(value,v) ).sum else 0.0)
}
/**
* Returns a lower bound for the subtree rooted at the variable
*/
def LB : Double = variable.domain.map(v => LB(v)).min
/**
* Returns a upper bound for the subtree rooted at the variable
*/
def UB : Double = variable.domain.map(v => UB(v)).min
// A leaf agent has no subtree so δ(d) = LB(d) = UB(d) for all value choices d and thus,
// LB is always equal to UB at a leaf.
/**
* Message handling
*/
override def receive: Receive = {
// When the variable agent is initiated
case Init(d, p, c) =>
solverAgent = sender
directory = d
parent = p
children = c
initialize()
// When the parent terminate
case Terminate(ctxt) =>
terminated = true
currentContext = ctxt
backtrack()
// When the parent agent sets the threshold value
case Threshold(thresholdValue,ctxt) =>
if (ctxt.isCompatible(currentContext)){
threshold = thresholdValue
maintainThresholdInvariant()
backtrack()
}
//
case Cost(lb, ub, ctxt) =>
//TODO
// When debugging mode is triggered
case Trace =>
trace = true
// Unexpected message
case msg@_ =>
println(s"WARNING: VariableAgent $variable receives a message from ${directory.variables(sender)} which was not expected: " + msg)
}
/**
* Initialize procedure
*/
def initialize() : Unit = {
if (debug) println(s"Agent $variable initializes")
threshold = 0.0
currentContext.fix(Map())
resetBound()// initiate lb/up
di = minimize(LB)// di ← d that minimizes LB(d)
//solverAgent ! Assign(di)
backtrack()
}
/**
* Backtrack procedure
*/
def backtrack() : Unit = {
if (debug) println(s"Agent $variable backtracks")
if (threshold ~= UB) {
di = minimize(UB)// di ← d that minimizes UB(d)
}else if(LB(di) > threshold){
di = minimize(LB)// di ← d that minimizes LB(d)
}
// Sends value to each lower priority neighbor;
neighbors().foreach{ neighbor =>
if (trace) println(s"$variable -> ${directory.variables(neighbor)} : Assign($di)")
neighbor ! Assign(di)
}
maintainAllocationInvariant()
if (threshold ~= UB){
if (terminated || isRootAgent) {
currentContext.fix(variable,di)
neighbors().foreach{ neighbor =>
if (trace) println(s"$variable -> ${directory.variables(neighbor)} : Terminate($currentContext)")
neighbor ! Terminate(currentContext)
}
solverAgent ! Assign(di)
context.stop(self)
}
}
// Sends Cost to parent
currentContext.fix(variable,di)
if (parent.isDefined){
if (trace) println(s"$variable -> ${parent.get} : Cost($LB, $UB, $currentContext) ")
directory.adr(parent.get) ! Cost(LB, UB, currentContext)
}
}
/**
* Maintains allocation invariant
*/
def maintainAllocationInvariant() : Unit = {
if (debug) println(s"Agent $variable maintains allocation invariant")
//we assume thresholdInvariant is satisfied
maintainThresholdInvariant()
while (threshold > ∂(di) + children.toSeq.map(xl => t(di, xl)).sum) {
//choose xl ∈Children where ub(di,xl)>t(di,xl);
val xl = children.find(xl => ub(di, xl) > t(di, xl))
if (xl.isDefined) t += ((di, xl.get) -> (t(di, xl.get) + 1.00))
}
while (threshold < ∂(di) + children.toSeq.map(xl => t(di, xl)).sum) {
//choose xl ∈Children where t(di,xl)>lb(di,xl);
val xl = children.find(xl => t(di, xl) > lb(di, xl))
if (xl.isDefined) t += ((di, xl.get) -> (t(di, xl.get) - 1.00))
}
currentContext.fix(variable,di)
children.foreach{ xl =>
if (trace) println(s"$variable -> $xl : Threshold(${t(di ,xl)}, $currentContext)")
directory.adr(xl) ! Threshold(t(di ,xl), currentContext)
}
}
/**
* Maintains threshold invariant
*/
def maintainThresholdInvariant() : Unit = {
if (debug) println(s"Agent $variable maintains threshold invariant")
if (threshold < LB) threshold = LB
if (threshold > UB) threshold = UB
}
}
// Copyright (C) Maxime MORGE 2020
package org.scadcop.dfs
import org.scadcop.problem.{DCOP,Variable}
/**
* Class representing a depth-first search tree for a DCOP
* @param root variable
*/
class DFS(val root : Variable){
var children : Set[DFS] = Set[DFS]()
/**
* String representation
*/
override def toString: String = {
root.id + children.mkString("[",",","]")
}
}
/**
* Factory for [[org.scadcop.dfs.DFS]] instances
*/
object DFS{
// Is the variables are marked
var marked : Map[Variable,Boolean] = Map[Variable,Boolean]()
/**
* Factory's method
* @param pb DCOP
*/
def apply(pb: DCOP): DFS = {
apply(pb,pb.variables.head)
}
/**
* Factory's method
* @param pb DCOP
* @param root variable
*/
def apply(pb: DCOP, root: Variable): DFS = {
pb.variables.foreach { v =>
marked += (v -> false)
}
buildDFS(pb, root)
}
/**
* Returns a DFS which is recursively built
*/
def buildDFS(pb: DCOP, root : Variable): DFS = {
val dfs = new DFS(root)
marked += (root -> true)
pb.linked(root).foreach{ v => // for each linked variable
if (! marked(v)) dfs.children += buildDFS(pb, v) // if the variable is not marked add subtree
}
dfs
}
}
\ No newline at end of file
// Copyright (C) Maxime MORGE 2020
package org.scadcop.problem
/**
* Class representing a binary constraint over two variables
* which valuates the cost of each couple of values
* @param variable1 the first variable
* @param variable2 the second variable
* @param cost the cost matrix
*/
class Constraint(val variable1: Variable, val variable2: Variable, val cost : Array[Array[Double]]){
/**
* String representation
*/
override def toString: String = {
var r = s"Cost(x${variable1.id})(x${variable2.id})\n"
for {
i <- variable1.domain.indices
j <- variable2.domain.indices
} r+=s"\t\tCost($i)($j) = ${cost(i)(j)}\n"
r
}
/**
* Returns true if the constraint is sound, i.e
* the cost of each couple of values is known and positive
*/
def sound() : Boolean = {
if (this.cost.length != variable1.domain.size) return false
for {
i <- variable1.domain.indices
} if (this.cost(i).length != variable1.domain.size) return false
for {
i <- variable1.domain.indices
j <- variable2.domain.indices
} if (cost(i)(j) < 0.0) return false
true
}
/**
* Returns true if the constraint is over a specific variable
*/
def isOver(variable: Variable) : Boolean = variable == variable1 || variable == variable2
/**
* Returns the cost when
* variableA is assigned to valueA
* and variableB is assigned to valueB
*/
def cost(variableA: Variable, valueA: Value, variableB: Variable, valueB: Value) : Double = {
var (index1,index2) = (0,0)
if (variableA == variable1) {
index1 = variable1.index(valueA)
index2 = variable2.index(valueB)
} else if (variableA == variable2) {
index2 = variable1.index(valueA)
index1 = variable2.index(valueB)
} else throw new RuntimeException(s"Constraint $this is not about $variableA and $variableB")
cost(index1)(index2)
}
}
// Copyright (C) Maxime MORGE 2020
package org.scadcop.problem
/**
* Class representing a full assignment or a partial context
* Each variable agent maintains a record of higher priority neighbors
* @param pb DCOP instance
* @param valuation for each variable
*/
class Context(val pb : DCOP, var valuation : Map[Variable, Value]){
/**
* Secondary constructor
*/
def this(pb : DCOP) = {
this(pb, Map[Variable, Value]())
}
/**
* Fix the value of a variable in the context
*/
def fix(variable : Variable, value: Value) : Unit ={
if (! variable.domain.contains(value))
throw new RuntimeException(s"$variable=$value is forbidden since $value is not in ${variable.domain}")
valuation += (variable -> value)
}
/**
* Fix the values of variables in the context
*/
def fix(values : Map[Variable, Value]) : Unit ={
values.foreach{ case (variable, value) =>
fix(variable,value)
}
}
/**
* Remove a variable in the context
*/
def remove(variable: Variable) : Unit = {
}
/**
* Returns true if the context is compatible with ctxt, i.e.
* they do not disagree on any variable assignment
*/
def isCompatible(ctxt : Context) : Boolean =
this.valuation.keys.toSet.intersect(ctxt.valuation.keys.toSet).forall( variable => this.valuation(variable) == ctxt.valuation(variable))
/**
* Returns the objective value of the assignment
*/
def objective() : Double = {
var sum = 0.0
pb.constraints.foreach{ c =>
sum += c.cost(c.variable1, valuation(c.variable1), c.variable2, valuation(c.variable2))
}
sum
}
/**
* Returns true if the pb is sound,
* i.e. each value is in the corresponding domain
*/
def sound() : Boolean = {
pb.variables.foreach{ variable =>
if (! variable.domain.contains(valuation(variable))) return false
}
true
}
/**
* Returns true if the assignment is full
*/
def isAssignment: Boolean = valuation.size == pb.variables.size
/**
* String representation of the assignment
*/
override def toString: String = valuation.mkString("[",", ","]")
}
// Copyright (C) Maxime MORGE 2020
package org.scadcop.problem
/**
* Class representing a Distributed Constraint Optimization Problem
* @param variables for which values must be given
* @param constraints represented as cost functions
*/
class DCOP(val variables: Set[Variable], val constraints : List[Constraint]){
/**
* String representation
*/
override def toString: String = s"Variables:\n"+
variables.map(v => v.description).mkString("\t", "\n\t", "\n")+
"Constraints:\n"+
constraints.mkString("\t", "\n\t", "\n")
/**
* Returns the variables linked to v
*/
def linked(v : Variable) : List[Variable] = {
var l = List[Variable]()
constraints.foreach{ c =>
if (c.variable1 == v) l = c.variable2 :: l
if (c.variable2 == v) l = c.variable1 :: l
}
l
}
/**
* Returns true if the DCOP is sound, i.e.
* 1 -there is at least one variable,
* 2- each variable has a unique id
* all the constraints are sound
*/
def sound() : Boolean = variables.nonEmpty && variables.map(_.id).size == variables.size && constraints.forall( c => c.sound())
/**
* Returns the constraints over a specific variable
*/
def constraints(variable: Variable) : List[Constraint] = constraints.filter(c => c.variable1 != variable && c.variable2 != variable)
}
// Copyright (C) Maxime MORGE 2020
package org.scadcop.problem
/**
* Object representing a toy DCOP instance
*/
object ToyExample {
val t = BooleanValue(false)
val f = BooleanValue(true)
val booleanDomain = List(t, f)
val x1 = new Variable(id = 1, booleanDomain)
val x2 = new Variable(id = 2, booleanDomain)
val x3 = new Variable(id = 3, booleanDomain)
val x4 = new Variable(id = 4, booleanDomain)
val cost = Array(Array(1.0, 2.0), Array(2.0, 0.0))
val c12 = new Constraint(x1, x2, cost)
val c13 = new Constraint(x1, x3, cost)
val c23 = new Constraint(x2, x3, cost)
val c24 = new Constraint(x2, x4, cost)
val pb = new DCOP(Set(x1, x2, x3, x4), List(c24, c23, c13, c12))
val a1 = new Context(pb)
a1.fix(Map(x1-> t, x2 -> t, x3 -> t, x4 -> t))
val a2 = new Context(pb)
a2.fix(Map(x1 -> f, x2 -> f, x3 -> f, x4 -> f))
}
// Copyright (C) Maxime MORGE 2020
package org.scadcop.problem
/**
* A domain for a variable is an ordered list of values
*/
package object domain {
type Domain = List[Value]
}
/**
* Abstract class for representing values of variables.
*/
abstract class Value{
/**
* Returns true if this value is equal to v
*/
def equal(v: Value): Boolean
/**
* Returns a string representation of the value
*/
override def toString: String
}
/**
* Class for representing values of nominal variables, i.e.
* variables whose domains are finite
*/
case class NominalValue(value : String) extends Value{
/**
* A secondary constructor.
*/
def this(intVal: Int) {
this(intVal.toString)
}
/**
* Returns true if this value is equal to v
*/
def equal(v: Value) : Boolean = {
v match {
case v : NominalValue => value == v.value
case _ => false
}
}
/**
* Returns a string representation of the value
*/
override def toString: String = value
}
/**
* Class for representing values of a boolean variable, i.e.
* variables whose values are true and false
*/
case class BooleanValue(value : Boolean) extends Value{
/**
* Returns true if this value is equal to v
*/
def equal(v: Value) : Boolean = {
v match {
case v : BooleanValue => value == v.value
case _ => false
}
}
/**
* Returns a string representation of the value
*/
override def toString: String = {
if (value) return "True"
"False"
}
}
\ No newline at end of file
// Copyright (C) Maxime MORGE 2020
package org.scadcop.problem
import domain.Domain
/**
* Class representing a variable
* @param id is the id of the variable
* @param domain is a finite and and discrete set of values
*/
class Variable(val id: Int, val domain: Domain){
/**
* Long string representation of the variable
*/
def description : String = s"x$id in "+domain.mkString("[", ", ", "]")
/**
* Short string representation of the variable
*/
override def toString : String = s"x$id"
/**
* Returns the index of a specific value
*/
def index(value : Value) : Int = domain.indexOf(value)
/**
* Return true if the variable is sound, i.e. a non-empty domain
*/
def sound() : Boolean = domain.nonEmpty
}
// Copyright (C) Maxime MORGE 2020
package org.scadcop.solver
import org.scadcop.problem.{DCOP,Context}
import org.scadcop.actor.{SolverAgent,Trace,Outcome,Start}
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.language.postfixOps
/**
* Distributed solver based on multi-agent negotiation process of single gift in order to minimize the rule
* @param pb to be solver
* @param system of Actors
*/
class DistributedSolver(pb : DCOP, system: ActorSystem) extends Solver(pb) {
val TIMEOUTVALUE : FiniteDuration = 6000 minutes // Default timeout of a run
implicit val timeout : Timeout = Timeout(TIMEOUTVALUE)
// Launch a new solverAgent
DistributedSolver.id+=1
val supervisor : ActorRef = system.actorOf(Props(classOf[SolverAgent], pb), name = "solverAgent"+DistributedSolver.id)
//TODO nb messages
/**
* Returns an allocation
*/
override def solve(): Context = {
if (debug) println("@startuml")
if (debug) println("skinparam monochrome true")
if (debug) println("hide footbox")
if (debug) {
for (i<- 1 to pb.variables.size) println(s"participant $i")
}
if (debug) supervisor ! Trace
val future = supervisor ? Start
val result = Await.result(future, timeout.duration).asInstanceOf[Outcome]
if (debug) println("@enduml")
//TODO count message
result.assignment
}
}
object DistributedSolver{
var id = 0
val debug = false
def main(args: Array[String]): Unit = {
import org.scadcop.problem.ToyExample._
println(pb)
val r = scala.util.Random
val system = ActorSystem("DistributedSolver" + r.nextInt.toString)
//The Actor system
val adoptSolver = new DistributedSolver(pb, system)// SingleGiftOnly SingleSwapAndSingleGift
adoptSolver.debug = true
val sol = adoptSolver.solve()
println(sol.toString)
println("Objective "+sol.objective())
System.exit(1)
}
}
// Copyright (C) Maxime MORGE 2020
package org.scadcop.solver
import org.scadcop.problem.{Context, DCOP}
/**
* Abstract class representing a solver
* @param pb DCOP instance
*/
abstract class Solver(val pb : DCOP) {
var debug = false
var solvingTime : Long = 0
/**
* Returns an assignment
*/
protected def solve() : Context
/**
* Returns an assignment and update solving time
*/
def run() : Context = {
val startingTime = System.nanoTime()
val assignment = solve()
solvingTime = System.nanoTime() - startingTime
if (! assignment.sound()) throw new RuntimeException(s"Solver: the outcome\n $assignment\n for\n $pb is not sound")
assignment
}
}
// Copyright (C) Maxime MORGE 2020
package org.scadcop.util
import akka.actor.ActorSystem
import org.scadcop.dfs.DFS
import org.scadcop.solver.DistributedSolver
/**
* Main application to test ADOPT on a toy example
*/
object Main {
import org.scadcop.problem.ToyExample._
def main(args: Array[String]): Unit = {
if (! pb.sound()) throw new RuntimeException("Pb is not sound")
println(pb)
if (! a1.sound()) throw new RuntimeException("A1 is not sound")
println("a1: " + a1)
println("a1's objective: " + a1.objective())
if (! a2.sound()) throw new RuntimeException("A2 is not sound")
println("a2: " + a2)
println("a2's objective: " + a2.objective())
val dfs =DFS(pb,x1)
println("DFS: "+dfs)
val system = ActorSystem("TestDistributedSolver")
val solver = new DistributedSolver(pb, system)
val assignment = solver.run()
println("Adopt outcome: " + assignment)
println("Adopt outcome objective: " + assignment.objective())
sys.exit(0)
}
}
// Copyright (C) Maxime MORGE 2019
package org.scadcop.util
/**
* Compare floating-point numbers in Scala
*
*/
object MathUtils {
implicit class MathUtils(x: Double) {
val precision = 0.000001
def ~=(y: Double): Boolean = {
if ((x - y).abs < precision) true else false
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment