Skip to content

Commit beaadaa

Browse files
authored
implement LRTAStarAgent (#76)
* 🚧 first shot at implementing LRTAStarAgent * adding 2D Map tentative implementations * adding map2d stuff to support writing lrta agent spec * fixing up a few things * fixing compile * change GoAction to sealed trait so don't have to use null for no action * added first spec for LTRAStar * added normal search A to F example for LRTAStar * added normal search F to A example for LRTAStar * added help method to simplify nested key get and fixed where could be min of empty list * should be using the updated result instead of original * if min of nil then return original H rather than max double as min * finds generalized solution for all A - F starting and goal states * use Eqv typeclass for safe comparison
1 parent 1624494 commit beaadaa

File tree

12 files changed

+703
-8
lines changed

12 files changed

+703
-8
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package aima.core.agent.basic
2+
3+
import aima.core.agent.StatelessAgent
4+
import aima.core.agent.basic.LRTAStarAgent.IdentifyState
5+
import aima.core.agent.basic.LRTAStarAgentState.{COST_ESTIMATES, RESULT}
6+
import aima.core.search.api.OnlineSearchProblem
7+
8+
/**
9+
*
10+
*
11+
* @author Shawn Garner
12+
*/
13+
final class LRTAStarAgent[PERCEPT, ACTION, STATE](
14+
identifyStateFor: IdentifyState[PERCEPT, STATE],
15+
onlineProblem: OnlineSearchProblem[ACTION, STATE],
16+
h: STATE => Double,
17+
stop: ACTION
18+
) extends StatelessAgent[PERCEPT, ACTION, LRTAStarAgentState[ACTION, STATE]] {
19+
20+
import LRTAStarAgentState.Implicits._
21+
22+
type RESULT_TYPE = RESULT[ACTION, STATE]
23+
type COST_ESTIMATES_TYPE = COST_ESTIMATES[STATE]
24+
25+
def lrtaCost(s: STATE, a: ACTION, sPrime: Option[STATE], H: COST_ESTIMATES_TYPE): Double = {
26+
val cost: Option[Double] = for {
27+
sPrime_ <- sPrime
28+
stepCost = onlineProblem.stepCost(s, a, sPrime_)
29+
tableLookupCost <- H.get(sPrime_)
30+
} yield stepCost + tableLookupCost
31+
32+
cost.getOrElse(h(s))
33+
}
34+
35+
override val agentFunction: AgentFunction = {
36+
case (percept, priorAgentState) =>
37+
val sPrime = identifyStateFor(percept)
38+
39+
if (onlineProblem.isGoalState(sPrime)) {
40+
41+
(stop, priorAgentState.copy(previousAction = Some(stop)))
42+
43+
} else {
44+
45+
val updatedH: COST_ESTIMATES_TYPE =
46+
priorAgentState.H.computeIfAbsent(sPrime, _ => h(sPrime))
47+
48+
val (updatedResult, updatedH2): (RESULT_TYPE, COST_ESTIMATES_TYPE) =
49+
(priorAgentState.previousState, priorAgentState.previousAction) match {
50+
case (Some(_s), Some(_a)) if !priorAgentState.result.get2(_s, _a).contains(sPrime) =>
51+
val resultOrigActionToState: Map[ACTION, STATE] =
52+
priorAgentState.result.getOrElse(_s, Map.empty[ACTION, STATE])
53+
val updatedResultActionToState
54+
: Map[ACTION, STATE] = resultOrigActionToState.put(_a, sPrime) // TODO: could be less verbose with lense
55+
56+
val finalResult = priorAgentState.result.put(_s, updatedResultActionToState)
57+
val priorActionsCost =
58+
onlineProblem.actions(_s).map(b => lrtaCost(_s, b, finalResult.get2(_s, b), updatedH))
59+
val minPriorActionCost = priorActionsCost match {
60+
case Nil => None
61+
case _ => Some(priorActionsCost.min)
62+
}
63+
val newH = minPriorActionCost match {
64+
case None => updatedH
65+
case Some(minCost) => updatedH.put(_s, minCost)
66+
}
67+
68+
(
69+
finalResult,
70+
newH
71+
)
72+
case _ =>
73+
(
74+
priorAgentState.result,
75+
updatedH
76+
)
77+
}
78+
79+
val newActions: List[ACTION] = onlineProblem.actions(sPrime)
80+
val newAction: ACTION = newActions match {
81+
case Nil => stop
82+
case _ => newActions.minBy(b => lrtaCost(sPrime, b, updatedResult.get2(sPrime, b), updatedH2))
83+
}
84+
85+
val updatedAgentState = priorAgentState.copy(
86+
result = updatedResult,
87+
H = updatedH2,
88+
previousState = Some(sPrime),
89+
previousAction = Some(newAction)
90+
)
91+
92+
(newAction, updatedAgentState)
93+
}
94+
}
95+
96+
}
97+
98+
final case class LRTAStarAgentState[ACTION, STATE](
99+
result: RESULT[ACTION, STATE],
100+
H: COST_ESTIMATES[STATE],
101+
previousState: Option[STATE], // s
102+
previousAction: Option[ACTION] // a
103+
)
104+
105+
object LRTAStarAgentState {
106+
107+
def apply[ACTION, STATE] =
108+
new LRTAStarAgentState[ACTION, STATE](
109+
result = Map.empty,
110+
H = Map.empty,
111+
previousState = None,
112+
previousAction = None
113+
)
114+
115+
type RESULT[ACTION, STATE] = Map[STATE, Map[ACTION, STATE]]
116+
type COST_ESTIMATES[STATE] = Map[STATE, Double]
117+
118+
object Implicits {
119+
120+
implicit class MapOps[K, V](m: Map[K, V]) {
121+
def put(k: K, v: V): Map[K, V] =
122+
m.updated(k, v)
123+
124+
def computeIfAbsent(k: K, v: K => V): Map[K, V] = {
125+
if (m.contains(k)) {
126+
m
127+
} else {
128+
put(k, v(k))
129+
}
130+
}
131+
132+
def transformValue(k: K, fv: Option[V] => V): Map[K, V] = {
133+
val oldValue = m.get(k)
134+
val newValue = fv(oldValue)
135+
m.updated(k, newValue)
136+
}
137+
138+
}
139+
140+
implicit class Map2Ops[K1, K2, V](m: Map[K1, Map[K2, V]]) {
141+
def get2(k1: K1, k2: K2): Option[V] =
142+
m.get(k1).flatMap(_.get(k2))
143+
144+
}
145+
146+
}
147+
}
148+
149+
object LRTAStarAgent {
150+
type IdentifyState[PERCEPT, STATE] = PERCEPT => STATE
151+
}

core/src/main/scala/aima/core/agent/basic/OnlineDFSAgent.scala

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import aima.core.agent.basic.OnlineDFSAgent.IdentifyState
55
import aima.core.agent.basic.OnlineDFSAgentState.{RESULT, UNBACKTRACKED, UNTRIED}
66
import aima.core.fp.Eqv
77
import aima.core.fp.Eqv.Implicits._
8+
import aima.core.search.api.OnlineSearchProblem
89

910
/**
1011
* <pre>
@@ -32,7 +33,7 @@ import aima.core.fp.Eqv.Implicits._
3233
*/
3334
final class OnlineDFSAgent[PERCEPT, ACTION, STATE: Eqv](
3435
identifyStateFor: IdentifyState[PERCEPT, STATE],
35-
onlineProblem: OnlineSearchProblem[STATE, ACTION],
36+
onlineProblem: OnlineSearchProblem[ACTION, STATE],
3637
stop: ACTION
3738
) extends StatelessAgent[PERCEPT, ACTION, OnlineDFSAgentState[ACTION, STATE]] {
3839

@@ -167,12 +168,6 @@ object OnlineDFSAgentState {
167168
}
168169
}
169170

170-
trait OnlineSearchProblem[STATE, ACTION] {
171-
def actions(s: STATE): List[ACTION]
172-
def isGoalState(s: STATE): Boolean
173-
def stepCost(s: STATE, a: ACTION, sPrime: STATE): Double
174-
}
175-
176171
object OnlineDFSAgent {
177172
type IdentifyState[PERCEPT, STATE] = PERCEPT => STATE
178173
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package aima.core.environment.map2d
2+
3+
/**
4+
* @author Shawn Garner
5+
*/
6+
sealed trait Map2DAction
7+
case object NoOp extends Map2DAction
8+
final case class Go(gotTo: String) extends Map2DAction
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package aima.core.environment.map2d
2+
3+
/**
4+
* @author Shawn Garner
5+
*/
6+
final case class InState(location: String) extends AnyVal
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package aima.core.environment.map2d
2+
3+
/**
4+
* @author Shawn Garner
5+
*/
6+
final case class IntPercept(value: Int) extends AnyVal

0 commit comments

Comments
 (0)