All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scroll.internal.support.RoleGroups.scala Maven / Gradle / Ivy

The newest version!
package scroll.internal.support

import org.chocosolver.solver.Model
import org.chocosolver.solver.Solution
import org.chocosolver.solver.variables.IntVar
import scroll.internal.Compartment
import scroll.internal.util.ReflectiveHelper

import scala.collection.mutable
import scala.reflect.ClassTag
import scala.reflect.classTag

trait RoleGroups {
  self: Compartment =>

  private[this] lazy val roleGroups = mutable.HashMap.empty[String, RoleGroup]

  private[this] sealed trait Constraint

  private[this] object AND extends Constraint

  private[this] object OR extends Constraint

  private[this] object XOR extends Constraint

  private[this] object NOT extends Constraint

  /**
    * Wrapping function that checks all available role group constraints for
    * all core objects and its roles after the given function was executed.
    * Throws a RuntimeException if a role group constraint is violated!
    *
    * @param func the function to execute and check role group constraints afterwards
    */
  def RoleGroupsChecked(func: => Unit): Unit = {
    func
    validate()
  }

  private[this] def validateOccurrenceCardinality(): Unit = {
    roleGroups.foreach { case (name, rg) =>
      val min = rg.occ._1
      val max = rg.occ._2
      val types = rg.types
      val actual = types.map(ts => plays.allPlayers.count(r => ts == ReflectiveHelper.classSimpleClassName(r.getClass.toString))).sum
      if (actual < min || max < actual) {
        throw new RuntimeException(s"Occurrence cardinality in role group '$name' violated! " +
          s"Roles '$types' are played $actual times but should be between $min and $max.")
      }
    }
  }

  private[this] def buildConstraintsMap(types: Seq[String], op: Constraint, model: Model, numOfTypes: Int, min: Int, max: CInt, rg: RoleGroup): Map[String, IntVar] = types.map(ts => op match {
    case AND => ts -> model.intVar("NUM$" + ts, 1)
    case OR => ts -> model.intVar("NUM$" + ts, 0, numOfTypes)
    case XOR => ts -> model.intVar("NUM$" + ts, 0, 1)
    case NOT => ts -> model.intVar("NUM$" + ts, 0)
    case _ =>
      throw new RuntimeException(s"Role group constraint of ($min, $max) for role group '${rg.name}' not possible!")
  }).toMap

  private[this] def solve(model: Model, types: Seq[String], constraintsMap: Map[String, IntVar], rg: RoleGroup): Seq[String] = {
    val solver = model.getSolver
    if (solver.solve()) {
      val resultRoleTypeSet = mutable.Set.empty[String]

      val solutions = mutable.ListBuffer.empty[Solution]
      do {
        val sol = new Solution(model)
        sol.record()
        solutions += sol
      } while (solver.solve())

      val allPlayers = plays.allPlayers.filter(p => !types.contains(ReflectiveHelper.classSimpleClassName(p.getClass.toString)))
      if (allPlayers.forall(p => {
        solutions.exists(s => {
          types.forall(t => {
            val numRole = plays.roles(p).count(r => t == ReflectiveHelper.classSimpleClassName(r.getClass.toString))
            if (numRole == s.getIntVal(constraintsMap(t))) {
              resultRoleTypeSet.add(t)
              true
            } else {
              false
            }
          })
        })
      })) {
        rg.evaluated = true
        return resultRoleTypeSet.toSeq
      }

    } else {
      throw new RuntimeException(s"Constraint set of role group '${rg.name}' unsolvable!")
    }
    // give up
    throw new RuntimeException(s"Constraint set for inner cardinality of role group '${rg.name}' violated!")
  }

  private[this] def eval(rg: RoleGroup): Seq[String] = {
    val model = new Model("MODEL$" + rg.hashCode())
    val types = rg.types
    val numOfTypes = types.size
    val sumName = "SUM$" + rg.name

    val (min, max) = (rg.limit._1, rg.limit._2)

    val (sum, op) = (min, max) match {
      case _ if max.compare(min) == 0 && min == numOfTypes => (model.intVar(sumName, numOfTypes), AND)
      case _ if min == 1 && max.compare(numOfTypes) == 0 => (model.intVar(sumName, 1, numOfTypes), OR)
      case _ if min == 1 && max.compare(1) == 0 => (model.intVar(sumName, 1), XOR)
      case _ if min == 0 && max.compare(0) == 0 => (model.intVar(sumName, 0), NOT)
      case _ => throw new RuntimeException(s"Role group constraint of ($min, $max) for role group '${rg.name}' not possible!")
    }

    val constraintsMap = buildConstraintsMap(types, op, model, numOfTypes, min, max, rg)
    model.post(model.sum(constraintsMap.values.toArray, "=", sum))
    solve(model, types, constraintsMap, rg)
  }

  private[this] def validateInnerCardinality(): Unit = {
    try {
      roleGroups.values.filter(!_.evaluated).foreach(eval)
    } finally {
      roleGroups.values.foreach(_.evaluated = false)
    }
  }

  /**
    * Checks all role groups.
    * Will throw a RuntimeException if a role group constraint is violated!
    */
  private[this] def validate(): Unit = {
    validateOccurrenceCardinality()
    validateInnerCardinality()
  }

  private[this] def addRoleGroup(rg: RoleGroup): RoleGroup = {
    if (roleGroups.exists { case (n, _) => n == rg.name }) {
      throw new RuntimeException(s"The RoleGroup ${rg.name} was already added!")
    } else {
      roleGroups(rg.name) = rg
      rg
    }
  }

  private[this] type CInt = Ordered[Int]

  trait Entry {
    def types: Seq[String]
  }

  object Types {
    def apply(ts: String*): Types = new Types(ts.map(ReflectiveHelper.typeSimpleClassName))
  }

  class Types(ts: Seq[String]) extends Entry {
    def types: Seq[String] = ts
  }

  case class RoleGroup(name: String, entries: Seq[Entry], limit: (Int, CInt), occ: (Int, CInt), var evaluated: Boolean = false) extends Entry {
    assert(0 <= occ._1 && occ._2 >= occ._1)
    assert(0 <= limit._1 && limit._2 >= limit._1)

    private[this] implicit def classTagToString(m: ClassTag[_]): String = ReflectiveHelper.simpleName(m.toString)

    def types: Seq[String] = entries.flatMap {
      case ts: Types => ts.types
      case rg: RoleGroup => eval(rg)
      case _ => throw new RuntimeException("Role groups can only contain a list of types or role groups itself!")
    }

    def containing(rg: RoleGroup*)
                  (limitLower: Int, limitUpper: CInt)
                  (occLower: Int, occUpper: CInt): RoleGroup =
      addRoleGroup(new RoleGroup(name, rg, (limitLower, limitUpper), (occLower, occUpper)))

    def containing[T1 <: AnyRef : ClassTag](limitLower: Int, limitUpper: CInt)
                                           (occLower: Int, occUpper: CInt): RoleGroup = {
      val entry = Types(classTag[T1])
      addRoleGroup(new RoleGroup(name, Seq(entry), (limitLower, limitUpper), (occLower, occUpper)))
    }


    def containing[T1 <: AnyRef : ClassTag, T2 <: AnyRef : ClassTag]
    (limitLower: Int, limitUpper: CInt)
    (occLower: Int, occUpper: CInt): RoleGroup = {
      val entry = Types(classTag[T1], classTag[T2])
      addRoleGroup(new RoleGroup(name, Seq(entry), (limitLower, limitUpper), (occLower, occUpper)))
    }

    def containing[T1 <: AnyRef : ClassTag, T2 <: AnyRef : ClassTag, T3 <: AnyRef : ClassTag]
    (limitLower: Int, limitUpper: CInt)
    (occLower: Int, occUpper: CInt): RoleGroup = {
      val entry = Types(classTag[T1], classTag[T2], classTag[T3])
      addRoleGroup(new RoleGroup(name, Seq(entry), (limitLower, limitUpper), (occLower, occUpper)))
    }

    def containing[T1 <: AnyRef : ClassTag, T2 <: AnyRef : ClassTag, T3 <: AnyRef : ClassTag, T4 <: AnyRef : ClassTag]
    (limitLower: Int, limitUpper: CInt)
    (occLower: Int, occUpper: CInt): RoleGroup = {
      val entry = Types(classTag[T1], classTag[T2], classTag[T3], classTag[T4])
      addRoleGroup(new RoleGroup(name, Seq(entry), (limitLower, limitUpper), (occLower, occUpper)))
    }


    def containing[T1 <: AnyRef : ClassTag, T2 <: AnyRef : ClassTag, T3 <: AnyRef : ClassTag, T4 <: AnyRef : ClassTag, T5 <: AnyRef : ClassTag]
    (limitLower: Int, limitUpper: CInt)
    (occLower: Int, occUpper: CInt): RoleGroup = {
      val entry = Types(classTag[T1], classTag[T2], classTag[T3], classTag[T4], classTag[T5])
      addRoleGroup(new RoleGroup(name, Seq(entry), (limitLower, limitUpper), (occLower, occUpper)))
    }
  }

  object RoleGroup {
    def apply(name: String): RoleGroup = new RoleGroup(name, Seq.empty, (0, 0), (0, 0))
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy