Skip to content

Commit 53a2fab

Browse files
authored
Merge branch 'master' into master
2 parents 1376a59 + 1b7ca38 commit 53a2fab

File tree

6 files changed

+609
-3
lines changed

6 files changed

+609
-3
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
language: scala
22
scala:
3-
- 2.11.8
3+
- 2.12.8
44
jdk:
55
- oraclejdk8
66
cache:

core/src/main/scala/aima/core/search/adversarial/Game.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package aima.core.search.adversarial
22

3-
import aima.core.agent.{Action, NoAction}
4-
53
final case class UtilityValue(value: Double) extends AnyVal
64

75
/**
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package aima.core.search.local
2+
3+
import aima.core.search.local.set.NonEmptySet
4+
5+
import scala.annotation.tailrec
6+
import scala.concurrent.duration.FiniteDuration
7+
import scala.util.Random
8+
9+
final case class Fitness(value: Double) extends AnyVal
10+
final case class Probability(value: Double) extends AnyVal // Need to make sure between 0.0 an 1.0
11+
12+
package set {
13+
final class NonEmptySet[A] private[set] (val value: Set[A])
14+
15+
object NonEmptySet {
16+
def apply[A](set: Set[A]): Either[String, NonEmptySet[A]] = {
17+
if (set.isEmpty) {
18+
Left("Set can not be empty")
19+
} else {
20+
Right(new NonEmptySet[A](set))
21+
}
22+
}
23+
}
24+
}
25+
26+
trait Deadline {
27+
def isOverDeadline: Boolean
28+
}
29+
30+
object Deadline {
31+
def start(timeLimit: FiniteDuration) = new Deadline {
32+
val start = System.currentTimeMillis()
33+
override def isOverDeadline: Boolean = {
34+
val current = System.currentTimeMillis()
35+
(current - start) > timeLimit.toMillis
36+
}
37+
}
38+
}
39+
40+
object Util {
41+
def normalize(probDist: List[Double]): List[Double] = {
42+
val total = probDist.sum
43+
if (total != 0.0d) {
44+
probDist.map(d => d / total)
45+
} else {
46+
List.fill(probDist.length)(0.0d)
47+
}
48+
}
49+
}
50+
51+
/**
52+
* function GENETIC-ALGORITHM(population, FITNESS-FN) returns an individual
53+
* inputs: population, a set of individuals
54+
* FITNESS-FN, a function that measures the fitness of an individual
55+
*
56+
* repeat
57+
* new_population <- empty set
58+
* repeat
59+
* x <- RANDOM-SELECTION(population, FITNESS-FN)
60+
* y <- RANDOM-SELECTION(population, FITNESS-FN)
61+
* child <- REPRODUCE(x, y)
62+
* if (small random probability) then child <- MUTATE(child)
63+
* add child to new_population
64+
* until SIZE(new_population) = SIZE(population)
65+
* population <- new_population
66+
* until some individual is fit enough, or enough time has elapsed
67+
* return the best individual in population, according to FITNESS-FN
68+
* --------------------------------------------------------------------------------
69+
* function REPRODUCE(x, y) returns an individual
70+
* inputs: x, y, parent individuals
71+
*
72+
* n <- LENGTH(x); c <- random number from 1 to n
73+
* return APPEND(SUBSTRING(x, 1, c), SUBSTRING(y, c+1, n))
74+
* </pre>
75+
*
76+
* Figure ?? A genetic algorithm. The algorithm is the same as the one
77+
* diagrammed in Figure 4.6, with one variation: in this more popular version,
78+
* each mating of two parents produces only one offspring, not two.
79+
*
80+
* @author Shawn Garner
81+
*/
82+
trait GeneticAlgorithm[Individual] {
83+
84+
type FitnessFunction = Individual => Fitness
85+
type FitEnough = Individual => Boolean
86+
type ReproductionFunction = (Individual, Individual, Random) => List[Individual]
87+
type MutationFunction = (Individual, Random) => Individual
88+
89+
def geneticAlgorithm(initialPopulation: NonEmptySet[Individual], fitnessFunction: FitnessFunction)(
90+
fitEnough: FitEnough,
91+
timeLimit: FiniteDuration,
92+
reproduce: ReproductionFunction,
93+
mutationProbability: Probability,
94+
mutate: MutationFunction
95+
): Individual = {
96+
val random = new Random()
97+
val deadline = Deadline.start(timeLimit)
98+
99+
@tailrec def recurse(currentPopulation: Set[Individual], newPopulation: Set[Individual]): Individual = {
100+
val x = randomSelection(currentPopulation, fitnessFunction)(random)
101+
val y = randomSelection(currentPopulation, fitnessFunction)(random)
102+
val children = reproduce(x, y, random)
103+
val mutated = {
104+
children.map { c =>
105+
if (isSmallRandomProbabilityOfMutation(mutationProbability, random)) {
106+
mutate(c, random)
107+
} else {
108+
c
109+
}
110+
}
111+
}
112+
113+
val updatedNewPop = newPopulation ++ mutated
114+
115+
if (updatedNewPop.size < currentPopulation.size) {
116+
recurse(currentPopulation, updatedNewPop)
117+
} else {
118+
val selected = selectBestIndividualIfReady(updatedNewPop, fitnessFunction)(fitEnough, deadline, random)
119+
selected match {
120+
case Some(ind) => ind
121+
case None => recurse(updatedNewPop, Set.empty[Individual])
122+
}
123+
124+
}
125+
}
126+
127+
recurse(initialPopulation.value, Set.empty[Individual])
128+
129+
}
130+
131+
def selectBestIndividualIfReady(population: Set[Individual], fitnessFunction: FitnessFunction)(
132+
fitEnough: FitEnough,
133+
deadline: Deadline,
134+
random: Random
135+
): Option[Individual] = {
136+
if (deadline.isOverDeadline || population.exists(fitEnough)) {
137+
138+
@tailrec def findBest(pop: List[Individual], best: Option[(Individual, Fitness)]): Option[Individual] =
139+
(pop, best) match {
140+
case (Nil, b) => b.map(_._1)
141+
case (current :: rest, None) =>
142+
val currentValue = fitnessFunction(current)
143+
findBest(rest, Some((current, currentValue)))
144+
case (current :: rest, Some(b)) =>
145+
val currentValue = fitnessFunction(current)
146+
if (currentValue.value > b._2.value) {
147+
findBest(rest, Some((current, currentValue)))
148+
} else if (currentValue.value == b._2.value) {
149+
if (random.nextBoolean()) {
150+
findBest(rest, Some((current, currentValue)))
151+
} else {
152+
findBest(rest, best)
153+
}
154+
} else {
155+
findBest(rest, best)
156+
}
157+
}
158+
159+
findBest(population.toList, None)
160+
} else {
161+
None
162+
}
163+
164+
}
165+
166+
def isSmallRandomProbabilityOfMutation(mutationProbability: Probability, random: Random): Boolean =
167+
random.nextDouble <= mutationProbability.value
168+
169+
def randomSelection(population: Set[Individual], fitnessFunction: FitnessFunction)(random: Random): Individual = {
170+
val populationList = population.toList
171+
val populationFitness = populationList.map(fitnessFunction)
172+
val fValues = Util.normalize(populationFitness.map(_.value))
173+
val probability = random.nextDouble()
174+
175+
val popWithFValues = populationList zip fValues
176+
@tailrec def selectByProbability(l: List[(Individual, Double)], totalSoFar: Double): Individual = l match {
177+
case first :: Nil => first._1 // if we are at end of list or only one element must select it
178+
case first :: rest =>
179+
val newTotal = totalSoFar + first._2
180+
if (probability <= newTotal) { // seems weird
181+
first._1
182+
} else {
183+
selectByProbability(rest, newTotal)
184+
}
185+
186+
}
187+
188+
selectByProbability(popWithFValues, 0.0d)
189+
}
190+
191+
}
192+
193+
object GeneticAlgorithm {
194+
object StringIndividual {
195+
196+
// function REPRODUCE(x, y) returns an individual
197+
def reproduce1(x: String, y: String, random: Random): List[String] = {
198+
// n <- LENGTH(x);
199+
val n = x.length
200+
// c <- random number from 1 to n
201+
val c = random.nextInt(n)
202+
// return APPEND(SUBSTRING(x, 1, c), SUBSTRING(y, c+1, n))
203+
List(
204+
x.substring(0, c) + y.substring(c, n)
205+
)
206+
}
207+
208+
// function REPRODUCE(x, y) returns a pair of individual
209+
def reproduce2(x: String, y: String, random: Random): List[String] = {
210+
// n <- LENGTH(x);
211+
val n = x.length
212+
// c <- random number from 1 to n
213+
val c = random.nextInt(n)
214+
// return APPEND(SUBSTRING(x, 1, c), SUBSTRING(y, c+1, n)) and APPEND(SUBSTRING(y, 1, c), SUBSTRING(x, c+1, n))
215+
List(
216+
x.substring(0, c) + y.substring(c, n),
217+
y.substring(0, c) + x.substring(c, n)
218+
)
219+
}
220+
221+
val alphabet: List[Char] = (('a' to 'z') ++ ('A' to 'Z')).toList
222+
223+
def mutate(child: String, random: Random): String = {
224+
val replacement: Char = alphabet(random.nextInt(alphabet.size))
225+
child.updated(random.nextInt(child.length), replacement)
226+
}
227+
}
228+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package aima.core.search.contingency
2+
3+
import scala.annotation.tailrec
4+
5+
/**
6+
*
7+
* <pre>
8+
* <code>
9+
* function AND-OR-GRAPH-SEARCH(problem) returns a conditional plan, or failure
10+
* OR-SEARCH(problem.INITIAL-STATE, problem, [])
11+
*
12+
* ---------------------------------------------------------------------------------
13+
*
14+
* function OR-SEARCH(state, problem, path) returns a conditional plan, or failure
15+
* if problem.GOAL-TEST(state) then return the empty plan
16+
* if state is on path then return failure
17+
* for each action in problem.ACTIONS(state) do
18+
* plan <- AND-SEARCH(RESULTS(state, action), problem, [state | path])
19+
* if plan != failure then return [action | plan]
20+
* return failure
21+
*
22+
* ---------------------------------------------------------------------------------
23+
*
24+
* function AND-SEARCH(states, problem, path) returns a conditional plan, or failure
25+
* for each s<sub>i</sub> in states do
26+
* plan<sub>i</sub> <- OR-SEARCH(s<sub>i</sub>, problem, path)
27+
* if plan<sub>i</sub> = failure then return failure
28+
* return [if s<sub>1</sub> then plan<sub>1</sub> else if s<sub>2</sub> then plan<sub>2</sub> else ... if s<sub>n-1</sub> then plan<sub>n-1</sub> else plan<sub>n</sub>]
29+
* </code>
30+
* </pre>
31+
*
32+
* @author Shawn Garner
33+
*/
34+
trait AndOrGraphSearch[ACTION, STATE] {
35+
def andOrGraphSearch(problem: NondeterministicProblem[ACTION, STATE]): ConditionPlanResult =
36+
orSearch(problem.initialState(), problem, Nil)
37+
38+
def orSearch(state: STATE, problem: NondeterministicProblem[ACTION, STATE], path: List[STATE]): ConditionPlanResult = {
39+
if (problem.isGoalState(state)) {
40+
ConditionalPlan.emptyPlan
41+
} else if (path.contains(state)) {
42+
ConditionalPlanningFailure
43+
} else {
44+
val statePlusPath = state :: path
45+
val actions: List[ACTION] = problem.actions(state)
46+
47+
@tailrec def recurse(a: List[ACTION]): ConditionPlanResult = a match {
48+
case Nil => ConditionalPlanningFailure
49+
case action :: rest =>
50+
andSearch(problem.results(state, action), problem, statePlusPath) match {
51+
case conditionalPlan: ConditionalPlan => newPlan(action, conditionalPlan)
52+
case ConditionalPlanningFailure => recurse(rest)
53+
}
54+
}
55+
56+
recurse(actions)
57+
}
58+
}
59+
60+
def andSearch(states: List[STATE],
61+
problem: NondeterministicProblem[ACTION, STATE],
62+
path: List[STATE]): ConditionPlanResult = {
63+
64+
@tailrec def recurse(currentStates: List[STATE], acc: List[(STATE, ConditionalPlan)]): ConditionPlanResult =
65+
currentStates match {
66+
case Nil => newPlan(acc)
67+
case si :: rest =>
68+
orSearch(si, problem, path) match {
69+
case ConditionalPlanningFailure => ConditionalPlanningFailure
70+
case plani: ConditionalPlan => recurse(rest, acc :+ (si -> plani))
71+
}
72+
}
73+
74+
recurse(states, List.empty)
75+
}
76+
77+
def newPlan(l: List[(STATE, ConditionalPlan)]): ConditionalPlan = l match {
78+
case (_, cp: ConditionalPlan) :: Nil => cp
79+
case ls => ConditionalPlan(ls.map(statePlan => ConditionedSubPlan(statePlan._1, statePlan._2)))
80+
81+
}
82+
83+
def newPlan(action: ACTION, plan: ConditionalPlan): ConditionalPlan =
84+
ConditionalPlan(ActionStep(action) :: plan.steps)
85+
86+
}
87+
88+
sealed trait Step
89+
final case class ActionStep[ACTION](action: ACTION) extends Step
90+
final case class ConditionedSubPlan[STATE](state: STATE, subPlan: ConditionalPlan) extends Step
91+
92+
sealed trait ConditionPlanResult
93+
case object ConditionalPlanningFailure extends ConditionPlanResult
94+
final case class ConditionalPlan(steps: List[Step]) extends ConditionPlanResult
95+
96+
object ConditionalPlan {
97+
val emptyPlan = ConditionalPlan(List.empty)
98+
def show[STATE, ACTION](conditionalPlan: ConditionalPlan,
99+
showState: STATE => String,
100+
showAction: ACTION => String): String = {
101+
102+
@tailrec def recurse(steps: List[Step], acc: String, lastStepAction: Boolean): String = steps match {
103+
case Nil => acc
104+
case ActionStep(a: ACTION) :: Nil => recurse(Nil, acc + showAction(a), true)
105+
case ActionStep(a: ACTION) :: rest => recurse(rest, acc + showAction(a) + ", ", true)
106+
case ConditionedSubPlan(state: STATE, subPlan) :: rest if lastStepAction =>
107+
recurse(rest, acc + s"if State = ${showState(state)} then ${show(subPlan, showState, showAction)}", false)
108+
case ConditionedSubPlan(_, subPlan) :: Nil =>
109+
recurse(Nil, acc + s" else ${show(subPlan, showState, showAction)}", false)
110+
case ConditionedSubPlan(_, subPlan) :: ActionStep(a) :: rest =>
111+
recurse(ActionStep(a) :: rest, acc + s" else ${show(subPlan, showState, showAction)}", false)
112+
case ConditionedSubPlan(state: STATE, subPlan) :: rest =>
113+
recurse(rest,
114+
acc + s" else if State = ${showState(state)} then ${show(subPlan, showState, showAction)}",
115+
false)
116+
}
117+
118+
recurse(conditionalPlan.steps, "[", true) + "]"
119+
}
120+
121+
}
122+
123+
trait NondeterministicProblem[ACTION, STATE] {
124+
def initialState(): STATE
125+
def actions(s: STATE): List[ACTION]
126+
def results(s: STATE, a: ACTION): List[STATE]
127+
def isGoalState(s: STATE): Boolean
128+
def stepCost(s: STATE, a: ACTION, childPrime: STATE): Double
129+
}

0 commit comments

Comments
 (0)