Skip to content

Commit 592d8a7

Browse files
Log Run details and Refactor Random Actuator/Sensors (#80)
* reorganize type parameters * update scalafmt * fix formatting * return run detail * build unreliable sensor and actuators from reliable ones * Actuator and Sensor signal failure by returning Optional value * abstract randomness * extract run details case class * rename to AgentRunSummary * scalafmt * remove nopercept * fix the unit test * remove optional from actuator act method
1 parent 1bf30c2 commit 592d8a7

File tree

13 files changed

+96
-88
lines changed

13 files changed

+96
-88
lines changed
Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
package aima.core.agent
22

3-
import aima.core.random.Randomness
3+
import aima.core.random.{DefaultRandomness, Randomness}
44

55
/**
66
* @author Shawn Garner
77
* @author Damien Favre
88
*/
99
trait Actuator[ENVIRONMENT, ACTION] {
10-
def act(action: ACTION, e: ENVIRONMENT): ENVIRONMENT //could be Action => E => E
10+
def act(action: ACTION, e: ENVIRONMENT): ENVIRONMENT
1111
}
1212

13-
trait UnreliableActuator[ENVIRONMENT, ACTION] extends Actuator[ENVIRONMENT, ACTION] with Randomness {
14-
def unreliably(original: ENVIRONMENT, reliability: Int = 50)(
15-
act: => ENVIRONMENT
16-
): ENVIRONMENT = {
17-
if (rand.nextInt(100) < reliability) act else original
13+
object UnreliableActuator {
14+
def fromActuator[ENVIRONMENT, ACTION](
15+
actuator: Actuator[ENVIRONMENT, ACTION]
16+
)(
17+
reliability: Int = 50,
18+
randomness: Randomness = new DefaultRandomness {}
19+
): Actuator[ENVIRONMENT, ACTION] = new Actuator[ENVIRONMENT, ACTION] {
20+
override def act(action: ACTION, e: ENVIRONMENT): ENVIRONMENT =
21+
randomness.unreliably(reliability)(actuator.act(action, e)).getOrElse(e)
1822
}
1923
}

core/src/main/scala/aima/core/agent/Agent.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,24 @@ trait Agent[ENVIRONMENT, PERCEPT, ACTION] {
99
def sensors: List[Sensor[ENVIRONMENT, PERCEPT]]
1010
def agentProgram: AgentProgram[PERCEPT, ACTION]
1111

12-
def run(e: ENVIRONMENT): ENVIRONMENT = {
13-
val actions: Seq[ACTION] = for {
14-
sensor <- sensors
15-
} yield agentProgram.agentFunction(sensor.perceive(e))
12+
def run(e: ENVIRONMENT): (ENVIRONMENT, AgentRunSummary[PERCEPT, ACTION]) = {
13+
val percepts: List[PERCEPT] = sensors.flatMap(_.perceive(e))
14+
val actions: List[ACTION] = percepts.map(agentProgram.agentFunction)
1615

17-
actions.foldLeft(e) { (env, action) =>
16+
val newEnvironment = actions.foldLeft(e) { (env, action) =>
1817
actuators.foldLeft(env) { (env2, actuator) =>
1918
actuator.act(action, env2)
2019
}
2120
}
21+
(newEnvironment, AgentRunSummary(percepts, actions))
2222
}
2323
}
2424

25+
case class AgentRunSummary[PERCEPT, ACTION](
26+
percepts: List[PERCEPT],
27+
actions: List[ACTION]
28+
)
29+
2530
trait AgentProgram[PERCEPT, ACTION] {
2631
type AgentFunction = PERCEPT => ACTION
2732

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package aima.core.agent
22

3-
import aima.core.random.Randomness
3+
import aima.core.random.{DefaultRandomness, Randomness}
44

55
trait Sensor[ENVIRONMENT, PERCEPT] {
6-
def perceive(e: ENVIRONMENT): PERCEPT
6+
def perceive(e: ENVIRONMENT): Option[PERCEPT]
77
}
88

9-
trait UnreliableSensor[ENVIRONMENT, PERCEPT] extends Sensor[ENVIRONMENT, PERCEPT] with Randomness {
10-
def unreliably(reliability: Int = 50)(perceive: => PERCEPT)(noPercept: PERCEPT): PERCEPT = {
11-
if (rand.nextInt(100) < reliability) perceive else noPercept
9+
object UnreliableSensor {
10+
def fromSensor[ENVIRONMENT, PERCEPT](
11+
sensor: Sensor[ENVIRONMENT, PERCEPT]
12+
)(
13+
reliability: Int = 50,
14+
randomness: Randomness = new DefaultRandomness {}
15+
): Sensor[ENVIRONMENT, PERCEPT] = new Sensor[ENVIRONMENT, PERCEPT] {
16+
override def perceive(e: ENVIRONMENT): Option[PERCEPT] =
17+
randomness.unreliably(reliability)(sensor.perceive(e)).flatten
1218
}
19+
1320
}

core/src/main/scala/aima/core/environment/vacuum/SimpleReflexVacuumAgentProgram.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,5 @@ class SimpleReflexVacuumAgentProgram extends SimpleReflexAgentProgram[VacuumPerc
1313
case LocationAPercept => RightMoveAction
1414
case LocationBPercept => LeftMoveAction
1515
case CleanPercept => NoAction
16-
case NoPercept => NoAction
1716
}
1817
}

core/src/main/scala/aima/core/environment/vacuum/VacuumEnvironment.scala

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import aima.core.random.DefaultRandomness
66
/**
77
* @author Shawn Garner
88
*/
9-
case class VacuumEnvironment(map: VacuumMap = VacuumMap()) extends Environment[VacuumEnvironment, VacuumPercept, VacuumAction] {
9+
case class VacuumEnvironment(map: VacuumMap = VacuumMap())
10+
extends Environment[VacuumEnvironment, VacuumPercept, VacuumAction] {
1011

1112
def addAgent(agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction]): VacuumEnvironment = {
1213
VacuumEnvironment(map.addAgent(agent))
@@ -26,7 +27,9 @@ case class VacuumMapNode(
2627
maybeAgent: Option[Agent[VacuumEnvironment, VacuumPercept, VacuumAction]] = None
2728
)
2829

29-
case class VacuumMap(nodes: Vector[VacuumMapNode] = Vector.fill(2)(VacuumMapNode())) extends DefaultRandomness {
30+
case class VacuumMap(
31+
nodes: Vector[VacuumMapNode] = Vector.fill(2)(VacuumMapNode())
32+
) extends DefaultRandomness {
3033
self =>
3134

3235
def isClean(): Boolean = {
@@ -38,10 +41,11 @@ case class VacuumMap(nodes: Vector[VacuumMapNode] = Vector.fill(2)(VacuumMapNode
3841
}
3942
}
4043

41-
def getAgentNode(agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction]): Option[VacuumMapNode] = nodes.collectFirst {
42-
case VacuumMapNode(dirtStatus, Some(a)) if agent == a =>
43-
VacuumMapNode(dirtStatus, Some(a))
44-
}
44+
def getAgentNode(agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction]): Option[VacuumMapNode] =
45+
nodes.collectFirst {
46+
case VacuumMapNode(dirtStatus, Some(a)) if agent == a =>
47+
VacuumMapNode(dirtStatus, Some(a))
48+
}
4549
def getDirtStatus(agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction]): Option[VacuumPercept] =
4650
getAgentNode(agent).map(_.dirtStatus)
4751

@@ -81,13 +85,18 @@ case class VacuumMap(nodes: Vector[VacuumMapNode] = Vector.fill(2)(VacuumMapNode
8185
VacuumMap(updatedNodes)
8286
}
8387

84-
def updateStatus(agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction], dirtStatusPercepts: DirtPercept): VacuumMap =
88+
def updateStatus(
89+
agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction],
90+
dirtStatusPercepts: DirtPercept
91+
): VacuumMap =
8592
updateByAgent(agent)(vacuumMapNode => vacuumMapNode.copy(dirtStatus = dirtStatusPercepts))
8693

8794
def removeAgent(agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction]): VacuumMap =
8895
updateByAgent(agent)(vacuumMapNode => vacuumMapNode.copy(maybeAgent = None))
8996

90-
private def updateByIndex(index: Int)(f: VacuumMapNode => VacuumMapNode): VacuumMap = {
97+
private def updateByIndex(
98+
index: Int
99+
)(f: VacuumMapNode => VacuumMapNode): VacuumMap = {
91100
val node = nodes.apply(index)
92101
val updatedNodes = nodes.updated(index, f(node))
93102
VacuumMap(updatedNodes)
Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,23 @@
11
package aima.core.environment.vacuum
22

3-
import aima.core.agent.{UnreliableActuator, Agent}
4-
import aima.core.random.DefaultRandomness
3+
import aima.core.agent.{Actuator, Agent}
54

65
/**
76
* @author Shawn Garner
87
*/
98
class SuckerActuator(val agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction])
10-
extends UnreliableActuator[VacuumEnvironment, VacuumAction]
11-
with DefaultRandomness {
12-
def act(action: VacuumAction, vacuum: VacuumEnvironment): VacuumEnvironment = {
13-
unreliably(vacuum) {
14-
action match {
15-
case Suck =>
16-
vacuum.copy(vacuum.map.updateStatus(agent, CleanPercept))
17-
case _ => vacuum
18-
}
19-
}
9+
extends Actuator[VacuumEnvironment, VacuumAction] {
10+
def act(action: VacuumAction, vacuum: VacuumEnvironment): VacuumEnvironment = action match {
11+
case Suck =>
12+
vacuum.copy(vacuum.map.updateStatus(agent, CleanPercept))
13+
case _ => vacuum
2014
}
2115
}
2216
class MoveActuator(val agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction])
23-
extends UnreliableActuator[VacuumEnvironment, VacuumAction]
24-
with DefaultRandomness { self =>
25-
def act(action: VacuumAction, vacuum: VacuumEnvironment): VacuumEnvironment = {
26-
unreliably(vacuum) {
27-
action match {
28-
case move: MoveAction =>
29-
vacuum.copy(vacuum.map.moveAgent(agent, move))
30-
case _ => vacuum
31-
}
32-
}
17+
extends Actuator[VacuumEnvironment, VacuumAction] {
18+
def act(action: VacuumAction, vacuum: VacuumEnvironment): VacuumEnvironment = action match {
19+
case move: MoveAction =>
20+
vacuum.copy(vacuum.map.moveAgent(agent, move))
21+
case _ => vacuum
3322
}
3423
}

core/src/main/scala/aima/core/environment/vacuum/percepts.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,3 @@ case object DirtyPercept extends DirtPercept
2222
object DirtPercept extends SetRandomness[DirtPercept] with DefaultRandomness {
2323
lazy val valueSet: Set[DirtPercept] = Set(CleanPercept, DirtyPercept)
2424
}
25-
26-
case object NoPercept extends VacuumPercept
Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
package aima.core.environment.vacuum
22

3-
import aima.core.agent.{Environment, UnreliableSensor, Agent}
4-
import aima.core.random.DefaultRandomness
3+
import aima.core.agent.{Agent, Sensor}
54

65
/**
76
* @author Shawn Garner
87
*/
9-
class AgentLocationSensor(val agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction], noPercept: VacuumPercept)
10-
extends UnreliableSensor[VacuumEnvironment, VacuumPercept]
11-
with DefaultRandomness {
12-
def perceive(vacuum: VacuumEnvironment): VacuumPercept =
13-
unreliably() {
14-
vacuum.map.getAgentLocation(agent).getOrElse(NoPercept)
15-
}(noPercept)
8+
class AgentLocationSensor(val agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction])
9+
extends Sensor[VacuumEnvironment, VacuumPercept] {
10+
def perceive(vacuum: VacuumEnvironment): Option[VacuumPercept] =
11+
vacuum.map.getAgentLocation(agent)
1612
}
1713

18-
class DirtSensor(val agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction], noPercept: VacuumPercept)
19-
extends UnreliableSensor[VacuumEnvironment, VacuumPercept]
20-
with DefaultRandomness { self =>
21-
def perceive(vacuum: VacuumEnvironment): VacuumPercept =
22-
unreliably() {
23-
vacuum.map.getDirtStatus(agent).getOrElse(NoPercept)
24-
}(noPercept)
14+
class DirtSensor(val agent: Agent[VacuumEnvironment, VacuumPercept, VacuumAction])
15+
extends Sensor[VacuumEnvironment, VacuumPercept] {
16+
def perceive(vacuum: VacuumEnvironment): Option[VacuumPercept] =
17+
vacuum.map.getDirtStatus(agent)
2518
}

core/src/main/scala/aima/core/random/Randomness.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import scala.util.Random
77
*/
88
trait Randomness {
99
def rand: Random
10+
def unreliably[A](reliability: Int = 50)(a: => A): Option[A] =
11+
if (rand.nextInt(100) < reliability) Some(a)
12+
else None
1013
}
1114

1215
trait DefaultRandomness extends Randomness {

core/src/test/scala/aima/core/environment/vacuum/ModelBasedReflexVacuumAgentSpec.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class ModelBasedReflexVacuumAgentSpec extends Specification {
2424
"should assume dirty after moving to location B" in new context {
2525
agent.agentFunction.apply(LocationAPercept)
2626
agent.agentFunction.apply(CleanPercept)
27-
agent.agentFunction.apply(NoPercept) must beLike {
27+
agent.agentFunction.apply(LocationBPercept) must beLike {
2828
case Suck => ok
2929
}
3030
}
@@ -45,7 +45,7 @@ class ModelBasedReflexVacuumAgentSpec extends Specification {
4545
"should assume dirty after moving to location A" in new context {
4646
agent.agentFunction.apply(LocationBPercept)
4747
agent.agentFunction.apply(CleanPercept)
48-
agent.agentFunction.apply(NoPercept) must beLike {
48+
agent.agentFunction.apply(LocationAPercept) must beLike {
4949
case Suck => ok
5050
}
5151
}

0 commit comments

Comments
 (0)