Curiosity Mars Rover (2020)

Mars Rover Curiosity Curiosity 2020

Mars Rover Curiosity Curiosity 2020

Mars Rover Curiosity AI that is based on a hybrid architecture that includes a reactive layer for its immediate decisions and uses the BDI (Beliefs, Desires, Intentions) paradigm for implementing its deliberative layer.

This practical problem will help us reinforce all. Space exploration is a fascinating topic that combines well with the area of AI and has millions of followers worldwide.

 

Since the conditions of space are pretty difficult and risky for humans, the use of robots is frequent and necessary. Therefore, the idea of using AI for machines that are involved in space exploration is logical, and many studies of it have been made in recent years.

 

The practical problem addressed throughout this blog will include a visual application (Windows Forms) that shows the execution of a Mars Rover Curiosity at any moment in a discrete environment of n x m (rows x columns).

 

This application simulates the Mars environment with various rocks that are considered obstacles by the agent and hidden spots of water or remnants of water. The program will also show us it's planning (sequence of actions conforming to a plan will be denoted in yellow) and how it manages beliefs, desires, and intentions.

 

The goal of a Mars Rover Curiosity is basically scientific research, and in our case, there is the very important task of finding vestiges of any type of water on Mars, plus trying to remain active and avoid obstacles.

 

What’s a Mars Rover Curiosity?

What’s a Mars Rover Curiosity

Mars is today a desolate, dry planet that when seen from a distance appears to resemble our home planet of Earth very little.

However, when approaching Mars’ orbit we can see on the surface what could have been ancient, now dried out lakes and canyons, suggesting that Mars may have harbored—three or four million years ago—not only water but also life.

 

Life in space is tough; it’s highly complicated for humans to survive out there, it’s risky, dangerous and reaching some of the closest planets could take many years, so in an effort to facilitate the research of other worlds, multiple space agencies (NASA, CSA, ESA, and so on) have been designing robots—or, as they are typically called, rovers—for the exploration and research of planets.

 

Mars Rover is an automated motor vehicle that is loaded up with cameras to analyze its surroundings, research instruments to dig in and maybe analyze interesting rocks, communication equipment with which to send pictures and data and receive commands, solar panels to provide energy to itself, and so on.

 

Rovers have the task of exploring Mars and collecting significant data that will hopefully lead to the conclusion of the existence of water on the planet in the past—or maybe to the discovery of ancient life.

 

Mars Rover Curiosity's tend to move very slowly, at nearly two inches per second (approximately 0.09 miles per hour). After all the trouble and cost that is involved when taking a rover to Mars, engineers prefer to play it safe and drive carefully; no one would like to see a $2.5 billion rover upside down because it was driving too fast.

 

Another important point: most rovers receive a daily set of commands or instructions from the team on Earth; these instructions tell the rover where to go or what to do.

In this sense, one could say that classic rover are not as autonomous as we might think; they do of course include some autonomous behavior because the team on Earth is not on Mars and cannot watch their every step live.

 

Therefore, the AI of the rover takes care of deciding when a rock is too big to go over (obstacle) or when the color and texture of a rock make it interesting to be examined. One could say that rover are sort of autonomous and follow orders very well, kind of like human soldiers do.

 

The mission of the rover is a two-sided job; on one side we have the engineers on Earth, planning their daily moves, their large-scale strategies, and so forth, and on the other side we have the rovers, executing these actions, exploring, collecting data, and sending it back to Earth.

 

In this blog, we will be demonstrating how to develop an AI for a completely autonomous Mars Rover Curiosity that will consider obstacles in the terrain and will be searching for water under a hybrid architecture that includes a BDI (Beliefs, Desires, Intentions) deliberative mechanism and uses statistics and probabilities for injecting itself with new beliefs that will be drawn as conclusions from its state (past history).

 

Note  Mars is usually known as the Red Planet because of its reddish tint in the night sky. In general, Mars is mostly rust colored because of the iron in its soil. When exposed to the small amount of oxygen in the Martian atmosphere, the iron oxidizes, or rusts. That “rust dust” can also blow into the air, turning the sky into a peach color.

 

Mars Rover Curiosity Architecture

 

Mars Rover Curiosity Architecture

Let’s take a brief moment to examine the hybrid architecture that we will be proposing for our Mars Rover Curiosity AI.

The architecture is composed of three layers (reactive, BDI, and planning); different percepts or events can cause a layer to execute. For instance, if there’s water at the rover’s current position then the reactive layer will act and conduct the rover to dig in that spot immediately.

 

If there’s a percept related to water in nearby areas the reactive layer will also be triggered. The rover will incorporate a variable or field named SenseRadius that will determine the circle surrounding it and represent its field of view; the rover will be capable of perceiving everything in that circle.

 

Since we are dealing with a discrete environment this circle will be an approximation of a real circle; in other words, it will be the discrete version of a circle.

Mars Rover Curiosity

 

Note  Mars Rover Curiosity like Spirit or Opportunity, both made by NASA, have fish-eye cameras or wide-angle cameras that allow them to catch a general view of the terrain in front of them. The photos these cameras take are analyzed to decide whether a certain rock on the path is too big to go over, and so on.

 

If the rover has some initial beliefs and there are no percepts of significant interest then control passes from the reactive layer to the BDI layer, where a process starts at the beliefs set; in this process, the beliefs set is updated.

 

A belief that we may have today could be proven wrong tomorrow. As for the rover, a location on the terrain where it believes there may be water could be incorrect, and as a result, this database of beliefs must be constantly updated as new percepts arrive.

 

In a second stage, desires are generated from beliefs. For the rover, its beliefs will consist of possible water locations and its desires will be these possible water locations ordered by proximity using the Manhattan distance (also known as Block distance) as a measure. Thus, going to the closest water location will become the current intention of the rover.

 

In order to accomplish its current intention, the rover uses its plan library (in the planning layer) and selects a plan fitting the selected intention. Since we are considering, in this example, only intentions associated with possible water locations our plan library will merely consist of one type of plan: pathfinding.

 

Path-finding algorithms solve the problem of finding the shortest path between two given points; these algorithms not only consider obstacles on the grid/terrain but also the cost of each possible path.

 

Some of its representatives are Breadth First Search (BFS), Dijkstra's algorithm, and A* search. For our rover, we developed BFS, the most inefficient of them all but also the simplest. The others perform better by using heuristics, dynamic programming, and so forth and avoid considering costly paths.

 

Once the rover has explored all of its beliefs it will wander around (making random moves) until it reaches a certain number of actions.

At this point, we will inject beliefs into the rover by using a data structure (dictionary) that maintains its state, or past history, as a set of visited cells along with their visit frequency (number of times it has visited a cell), and a deliberation process that consists of applying simple concepts of probability and statistics.

 

In this deliberation process the terrain known by the rover is divided into four equal (or almost equal) sectors (could be divided into 2n sectors for further precision), and for each sector and each (location, frequency_visits) pair in that sector we calculate the relative frequency and sum up the results obtained in each individual sector, having as the final result four Total Relative Frequency values (one for each sector). 

 

In the end, the rover will choose to “inject” the belief of water located in a corner of the sector with the lowest Total Relative Frequency, which should be the one least visited in the past.

 

We could say that this approach is pretty much a heuristic; i.e., we have specific knowledge about this problem and we are embedding it, trying to achieve a better behavior from the rover in its task.

 

This heuristic and others associated with this problem will be very simple and even naïve; the purpose right now is to illustrate how to create a hybrid agent architecture. Therefore, heuristics will not be at the core of this blog.

 

As a quick note, the strategy or heuristic where we always choose a corner of the selected sector for injecting a belief can be greatly improved in the same way the sector division and selection processes can be greatly improved.

 

Now that we have gotten a glimpse of our rover’s architecture and how it will actually make decisions every step of the way, it’s time to present its code.

 

Mars Rover Curiosity Code

Mars Rover Curiosity Code

The Mars Rover Curiosity is coded in a C# class containing the following fields, properties, and constructor (Listing1).

 

Listing 1. Mars Rover Curiosity Fields, Variables, and Constructor

public class MarsRover
{
public Mars Mars { get; set; }
public List<Belief> Beliefs { get; set; } public Queue<Desire> Desires { get; set; }
public Stack<Intention> Intentions { get; set; }
public List<Plan> PlanLibrary { get; set; }
public int X { get; set; } public int Y { get; set; }
public int SenseRadius { get; set; }
public double RunningOverThreshold { get; set; }
// Identifies the last part of the terrain seen by the Rover
public List<Tuple<int, int>> CurrentTerrain { get; set; } public Plan CurrentPlan { get; set; }
public List<Tuple<int, int>> WaterFound { get; set; } private double[,] _terrain;
private static Random _random;
private Dictionary<Tuple<int, int>, int> _perceivedCells; private int _wanderTimes;
private const int WanderThreshold = 10;
public MarsRover(Mars mars, double [,] terrain, int x, int y, IEnumerable<Belief> initialBeliefs, double runningOver, int senseRadious)
{
Mars = mars;
X = x;
Y = y;
_terrain = new double[terrain.GetLength(0), terrain­.GetLength(1)]; Array.Copy(terrain, _terrain, terrain.GetLength(0) * terrain.GetLength(1));
Beliefs = new List<Belief>(initialBeliefs); Desires = new Queue<Desire>(); Intentions = new Stack<Intention>(); PlanLibrary = new List<Plan>
{
new Plan(TypesPlan.
PathFinding, this),
};
WaterFound = new List<Tuple<int, int>>();
RunningOverThreshold = runningOver;
SenseRadius = senseRadious;
CurrentTerrain = new List<Tuple<int, int>>();
_random = new Random();
_perceivedCells = new Dictionary<Tuple<int, int>, int>();
}
}

 

The MarsRover class contains the following fields and variables:

 MarsRover class

Mars: an object-oriented representation of the world or environment of Mars. The agent will use this object to inquire about water locations and obstacles on the actual terrain of Mars.

X, Y: are both integers that represent the current position of the rover in the grid/Mars terrain.

 

_terrain: matrix representing the Mars world or terrain as the rover has it conceived initially, before landing there and before it can be updated by means of perceptions. It is like a preconception of Mars given by engineers; it’s their map and could have mistakes, so it must be updated.

 

Beliefs: list representing the set of beliefs the rover has; these could have come from a set of initial beliefs coded by engineers before the rover landed on Mars, like for instance, WaterAt(2,3), etc., or the beliefs that the rover injects itself later through some deliberative logic process

 

Desires: queue representing the set of desires the rover has; desires are born from beliefs and updated considering current intentions (if any). In the case of the rover, desires will consist of probable water locations, always ordered or prioritized by proximity.

 

Intentions: a stack of intentions the rover has; the one at the top represents the current intention and the one for which there’s a plan in motion

 

PlanLibrary: represents a list of plans the rover can execute depending on the intention taken

Waterford: list of water locations found on Mars (if any)

RunningOverThreshold: the double value that indicates the threshold by which rocks on the terrain are considered obstacles for the rover

SenseRadius: an integer value that represents the radius of vision of the rover; i.e., the radius of the circle whose center is the current position of the rover and determines its “sight” around

 

CurrentTerrain: represents the current terrain of the rover; i.e., the one defined by the circle of radius SenseRadius. This data structure is updated as the rover moves.

 

CurrentPlan: represents the current plan being executed by the rover

 

_random: variable for obtaining random values (for when the rover wanders around)

 

_perceivedCells: data structure storing the number of times a cell has been visited. It’s used for the Statistics-Probability component of the rover in deciding where to inject a belief of water when it has wandered around long enough.

 

_wanderTimes: integer value conveying the number of times the rover has wandered around

 

WanderThreshold: an integer value that determines the number of actions the rover can take as “wandering around.” Once the rover executes WanderThreshold actions it will stop wandering and will auto inject a belief.

 

The Mars object (representation of Mars world) uses the class shown in Listing 2 as a blueprint.

 Mars Rover

Listing 2. Mars Class

public class Mars
{
private readonly double[, ] _terrain;
public Mars(double[, ] terrain)
{
_terrain = new double[terrain.GetLength(0), terrain.GetLength(1)];
Array.Copy(terrain, _terrain, terrain.GetLength(0)
* terrain.GetLength(1));
}
public double TerrainAt(int x, int y)
{
return _terrain[x, y];
}
public bool WaterAt(int x, int y)
{
return _terrain[x, y] < 0;
}
}
 
The Mars-class is pretty straightforward;
 
it incorporates a matrix describing the terrain(elevations) and two methods that allow the rover to inquire about the situation of the environment at a given location. This terrain represents the real Martian terrain;
 
 
the rover also incorporates a representation of Mars’ environment, but this is a representation based on engineers’ maps and so forth.It’ s not going to be as accurate as the actual terrain. Thus, the rover will have to deal with this object to make sure its data on the Martian environment is accurate and, if not, update it.
 
 
In order to work with beliefs, desires, and intentions we code them all as classes. The Intention class inherits from the Desire class(Listing 3).
 
 Mars Rover class
 
 
Listing 3. Belief, Desire, and Intention Classes
 
public class Belief
{
public TypesBelief Name {
get;
set;
}
public dynamic Predicate;
public Belief(TypesBelief name, dynamic predicate) {
Name = name;
Predicate = predicate;
}
public override string ToString()
{
var result = "";
var coord = Predicate as List < Tuple < int,
int >> ;
foreach(var c in coord)
result += Name + " (" + c.Item1 + "," + c.Item2
+ ")" + "\n";
return result;
}
}
public class Desire
{
public TypesDesire Name {
get;
set;
}
public dynamic Predicate;
public List < Desire > SubDesires {
get;
set;
}
public Desire() {
SubDesires = new List < Desire > ();
}
public Desire(TypesDesire name)
{
Name = name;
SubDesires = new List < Desire > ();
}
public Desire(TypesDesire name, dynamic predicate) {
Name = name;
Predicate = predicate;
SubDesires = new List < Desire > ();
}
public Desire(TypesDesire name, IEnumerable < Desire >
subDesires)
{
Name = name;
SubDesires = new List < Desire > (subDesires);
}
public Desire(TypesDesire name, params Desire[]
subDesires)
{
Name = name;
SubDesires = new List < Desire > (subDesires);
}
public List < Desire > GetSubDesires()
{
if (SubDesires.Count == 0)
return new List < Desire > () {
this
};
var result = new List < Desire > ();
foreach(var desire in SubDesires) result.AddRange(desire.GetSubDesires());
return result;
}
public override string ToString()
{
return Name.ToString() + "\n";
}
}
public class Intention: Desire
{
public static Intention FromDesire(Desire desire)
{
var result = new Intention
{
Name = desire.Name,
SubDesires = new List < Desire >
(desire.SubDesires),
Predicate = desire.Predicate
};
return result;
}
}

Beliefs are usually encoded as predicates, so we included a dynamic (could be anything) Predicate property to represent them.

 

In this case, the rover will have as predicate a List<Tuple<int, int>> indicating beliefs of water locations. To adapt the class to hold different types of predicates, only the override ToString() method would need to change.

 

The GetSubDesires() method will be in charge of getting leaves from the desires tree. 

 

Finally, intentions inherit from desires. Remember: Intentions are a subset of desires, and we may have multiple desires, but not all of them need to be realistic at a given time; therefore, intentions are those desires to which we decide to commit at some point.

 

To be able to convert a desire into an intention we included the FromDesire() method. To define and easily work with a finite set of beliefs, desires, percepts, actions, and so on we declared the following (Listing 4) enums.

 Mars Rover

Listing 4. Enum for Beliefs, Desires, Percepts, Plans, and Actions

public enum TypePercept

{
WaterSpot, Obstacle, MoveUp, MoveDown, MoveLeft, MoveRight
}
public enum TypesBelief
{
PotentialWaterSpots, ObstaclesOnTerrain
}
public enum TypesDesire
{
FindWater, GotoLocation, Dig
}
public enum TypesPlan
{
PathFinding
}
public enum TypesAction
{
MoveUp, MoveDown, MoveLeft, MoveRight, Dig, None
}

 

To be able to handle percepts and plans a lot better we will incorporate into our program Percept and Plan classes as illustrated in Listing 5.

 Mars Rover plan

Listing 5. Percept and Plan Classes

public class Percept

{
public TypePercept Type {
get;
set;
}
public Tuple < int, int > Position {
get;
set;
}
public Percept(Tuple < int, int > position, TypePercept percept)
{
Position = position;
Type = percept;
}
}
public class Plan
{
public TypesPlan Name {
get;
set;
}
public List < Tuple < int, int >> Path {
get;
set;
}
private MarsRover _rover;
public Plan(TypesPlan name, MarsRover rover)
{
Name = name;
Path = new List < Tuple < int, int >> ();
_rover = rover;
}
public TypesAction NextAction()
{
if (Path.Count == 0)
return TypesAction.None;
var next = Path.First();
Path.RemoveAt(0);
if (_rover.X > next.Item1) return TypesAction.MoveUp;
if (_rover.X < next.Item1) return TypesAction.MoveDown;
if (_rover.Y < next.Item2) return TypesAction.MoveRight;
if (_rover.Y > next.Item2) return TypesAction.MoveLeft;
return TypesAction.None;
}
public void BuildPlan(Tuple < int, int > source,
Tuple < int, int > dest)
{
switch (Name)
{
case TypesPlan.PathFinding:
Path = PathFinding(source.Item1,
source.Item2, dest.Item1, dest.Item2).
Item2;
break;
}
}
private Tuple < Tuple < int, int > , List < Tuple < int, int >>> PathFinding(int x1, int y1, int x2, int y2) {
var queue = new Queue < Tuple < Tuple < int,
int > , List < Tuple < int, int >>> > ();
queue.Enqueue(new Tuple < Tuple < int, int > , List < Tuple < int, int >>> (new Tuple < int, int > (x1, y1), new List < Tuple < int, int >> ()));
var hashSetVisitedCells = new HashSet < Tuple < int,
int >> ();
while (queue.Count > 0)
{
var currentCell = queue.Dequeue();
var currentPath = currentCell.Item2;
hashSetVisitedCells.Add(currentCell.Item1);
var x = currentCell.Item1.Item1;
var y = currentCell.Item1.Item2;
if (x == x2 && y == y2)
return currentCell;
// Up
if (_rover.MoveAvailable(x - 1, y) &&
!hashSetVisitedCells.Contains(new Tuple < int,
int > (x - 1, y)))
{
var pathUp = new List < Tuple < int,
int >> (currentPath);
pathUp.Add(new Tuple < int, int > (x - 1, y));
queue.Enqueue(new Tuple < Tuple < int, int > , List < Tuple < int, int >>> (new Tuple < int, int > (x - 1, y), pathUp));
}
// Down
if (_rover.MoveAvailable(x + 1, y) &&
!hashSetVisitedCells.Contains(new Tuple < int,
int > (x + 1, y)))
{
var pathDown = new List < Tuple < int,
int >> (currentPath);
pathDown.Add(new Tuple < int, int > (x + 1, y));
queue.Enqueue(new Tuple < Tuple < int, int > , List < Tuple < int, int >>> (new Tuple < int, int > (x + 1, y), pathDown));
}
// Left
if (_rover.MoveAvailable(x, y - 1) &&
!hashSetVisitedCells.Contains(new Tuple < int,
int > (x, y - 1)))
{
var pathLeft = new List < Tuple < int,
int >> (currentPath);
pathLeft.Add(new Tuple < int, int > (x, y - 1));
queue.Enqueue(new Tuple < Tuple < int, int > , List < Tuple < int, int >>> (new Tuple < int, int > (x, y - 1), pathLeft));
}
// Right
if (_rover.MoveAvailable(x, y + 1) &&
!hashSetVisitedCells.Contains(new Tuple < int,
int > (x, y + 1)))
{
var pathRight = new List < Tuple < int,
int >> (currentPath);
pathRight.Add(new Tuple < int, int > (x, y + 1));
queue.Enqueue(new Tuple < Tuple < int, int > , List < Tuple < int, int >>> (new Tuple < int, int > (x, y + 1), pathRight));
}
}
return null;
}
public bool FulFill()
{
return Path.Count == 0;
}
}

The Percept class is very simple; we are merely using it to make it easier for us to know where a percept has occurred. By using this class we can save the percept location. The Plan class, on the other hand, is a bit more complicated.

 

The Plan class contains a property List<Tuple<int, int>> Path, which defines the Path the agent created as a result of executing a plan; in this case, a path-finding plan. The BuildPlan() method will allow us to build different types of plans. It’s supposed to act as a plan-selection mechanism. 

 

The NextAction() method updates the Path property by returning and deleting the next action to execute in the present plan. Finally, the PathFinding() method implements the Breadth First Search (BFS) algorithm for finding the optimal route from a given source to a given destination or location in the terrain.

 

We’ll see more of this algorithm in a future blog; for now let us consider it an essential algorithm for different graph-related tasks and remember that it starts at the source, discovering new steps of the path from source to destination and escalating by levels.

 

For this purpose it uses a queue for enqueuing all non-visited neighbors of the cell being examined at the time.

 

The FulFill() method determines when a plan has been completely executed.

 

Now that we have gotten acquainted with all the classes that our Mars Rover Curiosity will be using, let’s dive into the Mars Rover Curiosity AI code. our Mars Rover Curiosity includes a GetPercepts() method (Listing 6) that provides a list of percepts perceived by the agent at the current time and in its radius of sight.

 Mars Rover

 

Listing 6.  GetPercepts() Method

public List < Percept > GetPercepts()
{
var result = new List < Percept > ();
if (MoveAvailable(X - 1, Y))
result.Add(new Percept(new Tuple < int, int >
(X - 1, Y), TypePercept.MoveUp));
if (MoveAvailable(X + 1, Y))
result.Add(new Percept(new Tuple < int, int >
(X + 1, Y), TypePercept.MoveDown));
if (MoveAvailable(X, Y - 1))
result.Add(new Percept(new Tuple < int, int >
(X, Y - 1), TypePercept.MoveLeft));
if (MoveAvailable(X, Y + 1)) result.Add(new Percept(new Tuple < int, int > (X, Y + 1), TypePercept.MoveRight));
result.AddRange(LookAround());
return result;
}

The GetPercepts() method makes use of the MoveAvailable() and LookAround() methods, both illustrated in Listing 7.

 Mars Rover

Listing 7.  MoveAvailable() and LookAround() Methods

public bool MoveAvailable(int x, int y)
{
return x >= 0 && y >= 0 && x < _terrain.GetLength(0) && y < _terrain.GetLength(1) && _terrain[x, y] < RunningOverThreshold;
}
private IEnumerable < Percept > LookAround()
{
return GetCurrentTerrain();
}
 
Since we want to code our Mars Rover Curiosity to be as generic as possible in the way it“ looks around”(one may have a different definition of what it is to look around), the final implementation of this functionality is given by the GetCurrentTerrain() method shown in Listing 8.
 
 

Listing 8.  GetCurrentTerrain() Method

public IEnumerable < Percept > GetCurrentTerrain()
{
var R = SenseRadius;
CurrentTerrain.Clear();
var result = new List < Percept > ();
for (var i = X - R > 0 ? X - R : 0; i <=X + R; i++) { for (var j=Y; Math.Pow((j - Y), 2) + Math. Pow((i - X), 2) <=Math.Pow(R, 2); j--) { if (j < 0 || i>= _terrain.GetLength(0))
break;
// In the circle result.AddRange(CheckTerrain(Mars. TerrainAt(i, j), new Tuple<int, int>(i, j)));
CurrentTerrain.Add(new Tuple < int, int > (i, j));
UpdatePerceivedCellsDicc(new Tuple < int, int > (i, j));
}
for (var j = Y + 1;
(j - Y) * (j - Y) + (i - X)
* (i - X) <=R * R; j++) { if (j>= _terrain.GetLength(1) || i >= _terrain.GetLength(0)) break;
// In the circle result.AddRange(CheckTerrain(Mars.
TerrainAt(i, j), new Tuple < int, int > (i, j)));
CurrentTerrain.Add(new Tuple < int, int > (i, j));
UpdatePerceivedCellsDicc(new Tuple < int, int > (i, j));
}
}
return result;
}

 

The method from Listing 8 includes several loops that depend on the circle circumference formula

( x - h)2 + ( y - k )2 = r2

where (h, k) represent the center of the circle, in this case the agent’s location; r represents the radius of the circle, or in this case the SenseRadius.

 

These loops allow the rover to track every cell at distance SenseRadius of its current location. Within these loops we make calls to the UpdatePerceivedCellsDicc() and CheckTerrain() methods (Listing 9).

 

The first simply updates the visited cells dictionary that we use in the Statistics and Probability component to inject new beliefs to the rover.

 

The latter checks a given cell from the terrain to see if it’s an obstacle or a water location. It also updates the internal _terrain data structure the rover has initially and maintains later by updating the value that corresponds to the perceived coordinate.

 

 Mars Rover

Listing 9.  UpdatePerceivedCellsDicc() and CheckTerrain() Methods

private void UpdatePerceivedCellsDicc(Tuple < int,

int > position)
{
if (!_perceivedCells.ContainsKey(position))
_perceivedCells.Add(position, 0);
_perceivedCells[position]++;
}
private IEnumerable < Percept > CheckTerrain(double cell, Tuple < int, int > position)
{
var result = new List < Percept > ();
if (cell > RunningOverThreshold) result.Add(new Percept(position, TypePercept.Obstacle));
else if (cell < 0)
result.Add(new Percept(position,
TypePercept.WaterSpot));
// Update the rover's internal terrain
_terrain[position.Item1, position.Item2] = cell;
return result;
}
 
The method responsible for generating the next action to be executed by the rover is the Action() method shown in Listing10.
 

Listing 10.  Action() Method

 
public TypesAction Action(List < Percept > percepts)
{
// Reactive Layer
if (Mars.WaterAt(X, Y) && !WaterFound.Contains(new Tuple < int, int > (X, Y)))
return TypesAction.Dig;
var waterPercepts = percepts.FindAll(p => p.Type == TypePercept.WaterSpot);
if (waterPercepts.Count > 0)
{
foreach(var waterPercept in waterPercepts)
{
var belief = Beliefs.FirstOrDefault(b => b.Name == TypesBelief.PotentialWaterSpots);
List < Tuple < int, int >> pred;
if (belief != null)
pred = belief.Predicate as List < Tuple < int, int >> ;
else
{
pred = new List < Tuple < int, int >> {
waterPercept.Position
};
Beliefs.Add(new Belief(TypesBelief.PotentialWaterSpots, pred));
}
if (!WaterFound.Contains
(waterPercept.Position))
pred.Add(waterPercept.Position);
else
{
pred.RemoveAll(
t => t.Item1 == waterPercept.
Position.Item1 && t.Item2 ==
waterPercept.Position.Item2);
if (pred.Count == 0)
Beliefs.RemoveAll(b => (b.Predicate as List < Tuple < int, int >> ).Count == 0);
}
}
if (waterPercepts.Any(p => !WaterFound.
Contains(p.Position)))
CurrentPlan = null;
}
if (Beliefs.Count == 0)
{
if (_wanderTimes == WanderThreshold)
{
_wanderTimes = 0;
InjectBelief();
}
_wanderTimes++;
return RandomMove(percepts);
}
if (CurrentPlan == null || CurrentPlan.FullFill())
{
// Deliberative Layer Brf(percepts);
Options();
Filter();
}
return CurrentPlan.NextAction();
}

 

In this methowe incorporate the reactive and deliberative layers of the agent. The first lines correspond to the reactive layer, and different scenarios are considered that demand an Fimmediate response:

 

\ 1.\ There’s water at the current location of the rover, and that spot has not been discovered before.

 

\ 2.\ There’s a percept of a possible water location in the surrounding areas (defined by the circle with radius SenseRadius) of the rover. In this case, and always checking that the possible water location has not been already found, we add a water belief to the rover.

 

\ 3.\ If the water location perceived at step 2 has not been previously found then the current plan is deleted. A new one considering the new belief will be built.

 

\ 4.\ If the rover has no beliefs it will execute a random action (Listing 11); i.e., wanders around. Once this “wandering around” reaches a certain number of actions (ten, in this case) then a belief is injected.

 

The four previous steps make up the reactive layer of our agent; the last part of the method composed of the Brf(), Options(), and Filter() methods represent the deliberative layer (BDI architecture). The InjectBelief() method is also part of this deliberative layer as it involves a “deliberative” process where the agent decides its next course of action.

 

 Mars Rover class

Listing 11.  RandomMove() Method

private TypesAction RandomMove(List < Percept > percepts)
{
var moves = percepts.FindAll(p => p.Type.
ToString().Contains("Move"));
var selectedMove = moves[_random.Next(0, moves.Count)];
switch (selectedMove.Type)
{
case TypePercept.MoveUp:
return TypesAction.MoveUp;
case TypePercept.MoveDown:
return TypesAction.MoveDown;
case TypePercept.MoveRight:
return TypesAction.MoveRight;
case TypePercept.MoveLeft:
return TypesAction.MoveLeft;
}
return TypesAction.None;
}

The Statistics and Probability component of the rover, and the one that allows it to inject beliefs based on its past history, is represented by the InjectBelief() method, which can be seen in Listing 12 along with its helper methods.

 

Listing 12.  InjectBelief(), SetRelativeFreq(), and RelativeFreq() Methods

private void InjectBelief()


{

    var halfC = _terrain.GetLength(1) / 2;
    var halfR = _terrain.GetLength(0) / 2;

    var firstSector = _perceivedCells.Where(k => k.Key.

        Item1 < halfR && k.Key.Item2 < halfC).ToList();

    var secondSector = _perceivedCells.Where(k => k.Key.

        Item1 < halfR && k.Key.Item2 >= halfC).ToList();

    var thirdSector = _perceivedCells.Where(k => k.Key.Item1 >= halfR && k.Key.Item2 < halfC).ToList();
    var fourthSector = _perceivedCells.Where(k => k.Key.Item1 >= halfR && k.Key.Item2 >= halfC).ToList();

    var freq1stSector = SetRelativeFreq(firstSector);
    var freq2ndSector = SetRelativeFreq(secondSector);
    var freq3rdSector = SetRelativeFreq(thirdSector);
    var freq4thSector = SetRelativeFreq(fourthSector);

    var min = Math.Min(freq1stSector, Math.Min(freq2ndSector, Math.Min(freq3rdSector, freq4thSector)));

    if (min == freq1stSector) Beliefs.Add(new Belief(TypesBelief.PotentialWaterSpots, new List < Tuple < int, int >> {
        new Tuple < int, int > (0, 0)
    }));

    else if (min == freq2ndSector)

        Beliefs.Add(new Belief(TypesBelief.Potential WaterSpots, new List < Tuple < int, int >> {
        new Tuple < int, int > (0, _terrain.GetLength(1) - 1)
    }));

    else if (min == freq3rdSector)

        Beliefs.Add(new Belief(TypesBelief.Potential WaterSpots, new List < Tuple < int, int >> {
        new Tuple < int, int > (_terrain.GetLength(0) - 1, 0)
    }));

    else

        Beliefs.Add(new Belief(TypesBelief.Potential WaterSpots, new List < Tuple < int, int >> {
        new Tuple < int, int > (_terrain.GetLength(0) - 1, _terrain.GetLength(1) - 1)
    }));

}

private double SetRelativeFreq(List < KeyValuePair < Tuple

    < int, int > , int >> cells)

{

    var result = 0.0;

    foreach(var cell in cells)

    result += RelativeFrequency(cell.Value, cells.Count);

    return result;

}

private double RelativeFrequency(int absFreq, int n) {

    return (double) absFreq / n;

}

As it was detailed in the last section, the relative frequency is calculated for every cell of a given sector and then summed up in the SetRelativeFreq() method to obtain the total frequency of the group of cells.

 

Note that in this case we decided to divide the terrain into four equal sectors, but you may decide to do it in as many sectors as you deem necessary or to the level of detail you believe necessary, like you would do in a QuadTree.

 

One could even decide to divide the terrain into a certain number of sectors considering the SenseRadius of the rover and the time it wanders around.

 

These values are all related, and most of them are considered in the heuristics attached to the rover. In this case—and seeking simplicity in the example proposed—we choose to attach truly naïve heuristics for the rover;

 

For instance, always injecting a water belief at a corner of the selected sector could be a bad idea in different scenarios, as it’s not going to work well every time.

 

Thus, the sector selection and cell-within-sector selection mechanisms need to be more generic for the rover to perform well in multiple environments. Let’s keep in mind that the heuristics presented here can be greatly improved, and as a result the rover will improve its performance.

 

Note  A QuadTree is a tree data structure where each internal node has exactly four children. They are often used to partition a two-­ dimensional space or region by recursively subdividing it into four quadrants or regions. Lastly, let’s examine the deliberative layer and all its methods, starting with the Beliefs Revision Function (Listing 13).

 

 Mars Rover

 

Listing13.  Brf() Method

public void Brf(List < Percept > percepts)
{
var newBeliefs = new List < Belief > ();
foreach(var b in Beliefs)
{
switch (b.Name)
{
case TypesBelief.PotentialWaterSpots:
var waterSpots = new List < Tuple < int,
int >> (b.Predicate);
waterSpots = UpdateBelief(TypesBelief.
PotentialWaterSpots, waterSpots);
if (waterSpots.Count > 0) newBeliefs.Add(new Belief(TypesBelief.PotentialWaterSpots, waterSpots));
break;
case TypesBelief.ObstaclesOnTerrain:
var obstacleSpots = new List < Tuple < int,
int >> (b.Predicate);
obstacleSpots = UpdateBelief(TypesBelief.ObstaclesOnTerrain, obstacleSpots);
if (obstacleSpots.Count > 0) newBeliefs.Add(new Belief(TypesBelief.ObstaclesOnTerrain, obstacleSpots));
break;
}
}
Beliefs = new List < Belief > (newBeliefs);
}

 

In the Brf() method we examine every belief (possible water locations, possible obstacle locations) and update them, creating a new set of beliefs. The UpdateBelief() method is illustrated in Listing 14.

 

Listing 14.  UpdateBelief() Method

 

private List < Tuple < int, int >> UpdateBelief(TypesBelief belief, IEnumerable < Tuple < int, int >> beliefPos)

{
var result = new List < Tuple < int,
int >> ();
foreach(var spot in beliefPos)
{
if (CurrentTerrain.Contains(new Tuple < int, int > (spot.Item1, spot.Item2))) {
switch (belief)
{
case TypesBelief.PotentialWaterSpots:
if (_terrain[spot.Item1, spot.Item2] >= 0)
continue;
break;
case TypesBelief.ObstaclesOnTerrain:
if (_terrain[spot.Item1, spot.Item2] < RunningOverThreshold)
continue;
break;
}
}
result.Add(spot);
}
return result;
}

In the UpdateBelief() method we check every belief against the currently perceived terrain. If there’s a wrong belief—like, for instance, we thought or believed we would find water at location (x, y) and it happens that we were just there and there’s nothing—then that belief must be deleted.

 

The Options() method, which is responsible for generating desires, is shown in Listing 15.

 

Listing 15.  Options() Method

 public void Options() {
Desires.Clear();
foreach(var b in Beliefs) {
if (b.Name == TypesBelief.PotentialWaterSpots) {
var waterPos = b.Predicate as List < Tuple < int,
int >> ;
waterPos.Sort(delegate(Tuple < int, int > tupleA, Tuple < int, int > tupleB) {
var distA = Manhattan Distance(tupleA, new Tuple < int, int > (X, Y));
var distB = Manhattan Distance(tupleB, new Tuple < int, int > (X, Y));
if (distA < distB) return 1;
if (distA > distB) return -1;
return 0;
});
foreach(var wPos in waterPos) Desires.Enqueue(new Desire(TypesDesire.FindWater, new Desire(TypesDesire.GotoLocation, new Desire(TypesDesire.Dig, wPos))));
}
}
}

 

We will consider only one type of desire—the desire to find water at specific locations. Thus, using the set of beliefs as a base, we generate desires and sort them by proximity using the distance (Listing 16) as the proximity measure.

 

Listing 16.  Manhattan Distance

public int ManhattanDistance(Tuple<int, int> x, Tuple<int, int> y)

{

return Math.Abs(x.Item1 - y.Item1) + Math.Abs(x.Item2 - y.Item2);

}

Using the set of desires, we push new intentions into our Intentions set in the Filter() method; if there’s no plan in motion for the current intention then we choose one using the ChoosePlan() method.

 

Listing 17.  Filter() and ChoosePlan() Methods

private void Filter()
{
Intentions.Clear();
foreach (var desire in Desires)
{
if (desire.SubDesires.Count > 0)
{
var primaryDesires = desire.
GetSubDesires();
primaryDesires.Reverse();
foreach (var d in primaryDesires)
Intentions.Push(Intention.
FromDesire(d));
}
else
Intentions.Push(Intention.
FromDesire(desire));
}
if (Intentions.Any() && !ExistsPlan())
ChoosePlan();
}
private void ChoosePlan()
{
var primaryIntention = Intentions.Pop(); var location = primaryIntention.Predicate as Tuple<int, int>;
switch (primaryIntention.Name)
{
case TypesDesire.Dig:
CurrentPlan = PlanLibrary.First(p => p.Name == TypesPlan.PathFinding); CurrentPlan.BuildPlan(new Tuple<int, int>(X, Y), location); break;
}
}

 

To conclude, the ExistsPlan() method determines if there’s a plan in motion, and the ExecuteAction() method executes the action selected by the agent (Listing18). The latter method is also responsible for updating the WaterFound data structure with the locations where water has been found.

mars_rover

 

Listing 18.  ExistsPlan() and ExecuteAction() Methods

 public bool ExistsPlan()
{
return CurrentPlan != null && CurrentPlan.Path.
Count > 0;
}
public void ExecuteAction(TypesAction action, List<Percept> percepts)
{
switch (action)
{
case TypesAction.MoveUp:
X -= 1;
break;
case TypesAction.MoveDown:
X += 1;
break;
case TypesAction.MoveLeft:
Y -= 1;
break;
case TypesAction.MoveRight:
Y += 1;
break;
case TypesAction.Dig:
WaterFound.Add(new Tuple<int, int>(X, Y)); break;
}
}

 

In the next section, we’ll take a look at our Mars Rover Curiosity in action as it is executed in a Windows Forms Application that we created for experimenting and seeing how its AI works on a test world.

 

Mars Rover Curiosity Visual Application

As mentioned at the beginning of this blog, we created a Windows Forms application with which to test our Mars Rover Curiosity and see how it would do on a test Mars world with hidden water locations and obstacles along the way.

 

This example will not only help us to understand how to set up the MarsRover and Mars classes, but it will also demonstrate how the AI presented during this blog will perform its decision-making process under different scenarios.

 

The complete details of the Windows Form application (Listing 19) are beyond the scope of this blog; we will simply present a fragment of it to illustrate to readers where the graphics are coming from. For further reference, the source code associated with this blog can be consulted.

 

rover-1

Listing 19.  Fragment of Windows Forms Visual Application Code

public partial class MarsWorld : Form

{
private MarsRover _marsRover;
private Mars _mars;
private int _n;
private int _m;
public MarsWorld(MarsRover rover, Mars mars, int n, int m) {
InitializeComponent();
_marsRover = rover;
_mars = mars;
_n = n;
_m = m;
}
private void TerrainPaint(object sender, PaintEventArgs e) {
var pen = new Pen(Color.Wheat);
var waterColor = new SolidBrush(Color.Aqua); var rockColor = new SolidBrush(Color.Chocolate); var cellWidth = terrain.Width/_n; var cellHeight = terrain.Height/_m;
for (var i = 0; i < _n; i++) e.Graphics.DrawLine(pen, new Point(i * cellWidth, 0), new Point(i * cellWidth, i * cellWidth + terrain.Height));
for (var i = 0; i < _m; i++) e.Graphics.DrawLine(pen, new Point(0, i * cellHeight), new Point(i * cellHeight + terrain.Width, i * cellHeight));
if (_marsRover.ExistsPlan())
{
foreach (var cell in _marsRover.CurrentPlan.Path)
{
e.Graphics.FillRectangle(new SolidBrush (Color.Yellow), cell.Item2 * cellWidth, cell.Item1 * cellHeight, cellWidth, cellHeight);
}
}
for (var i = 0; i < _n; i++)
{
for (var j = 0; j < _m; j++)
{
if (_mars.TerrainAt(i, j) > _marsRover. RunningOverThreshold)
e.Graphics.DrawImage(new Bitmap("obstacle-transparency.png"), j*cellWidth, i*cellHeight, cellWidth, cellHeight);
if (_mars.WaterAt(i, j)) e.Graphics.DrawImage(new Bitmap("water-­ transparency.png"), j * cellWidth,
i * cellHeight, cellWidth, cellHeight);
// Draw every belief in white
foreach (var belief in _marsRover.Beliefs)
{
var pred = belief.Predicate as
List<Tuple<int, int>>;
if (pred != null && !pred.Contains(new Tuple<int, int>(i, j)))
continue;
if (belief.Name == TypesBelief.
ObstaclesOnTerrain)
{
e.Graphics.DrawImage(new Bitmap("obstacle-transparency. png"), j * cellWidth, i * cellHeight, cellWidth, cellHeight); e.Graphics.DrawRectangle(new Pen(Color.Gold, 6), j * cellWidth, i * cellHeight, cellWidth, cellHeight);
}
if (belief.Name == TypesBelief.
PotentialWaterSpots)
{
e.Graphics.DrawImage(new Bitmap("water-transparency.png"), j * cellWidth, i * cellHeight, cellWidth, cellHeight); e.Graphics.DrawRectangle(new Pen(Color.Gold, 6), j * cellWidth, i * cellHeight, cellWidth, cellHeight);
}
}
}
}
e.Graphics.DrawImage(new Bitmap("rover-transparency.png"), _marsRover.Y * cellWidth,_marsRover.X * cellHeight, cellWidth, cellHeight);
var sightColor = Color.FromArgb(80, Color.Lavender); _marsRover.GetCurrentTerrain();
foreach (var cell in _marsRover.CurrentTerrain)
e.Graphics.FillRectangle(new SolidBrush (sightColor), cell.Item2 * cellWidth, cell. Item1 * cellHeight, cellWidth, cellHeight);
}
private void TimerAgentTick(object sender, EventArgs e) {
var percepts = _marsRover.GetPercepts(); agentState.Text = "State: Thinking ..."; agentState.Refresh();
var action = _marsRover.Action(percepts); _marsRover.ExecuteAction(action, percepts);
var beliefs = UpdateText(beliefsList, _marsRover.Beliefs);
var desires = UpdateText(beliefsList, _marsRover.Desires);
var intentions = UpdateText(beliefsList, _marsRover.Intentions);
if (beliefs != beliefsList.Text) beliefsList.Text = beliefs; if (desires != desiresList.Text) desiresList.Text = desires;
if (intentions != intentionsList.Text)
intentionsList.Text = intentions;
foreach (var wSpot in _marsRover.WaterFound)
{
if (!waterFoundList.Items.Contains(wSpot))
waterFoundList.Items.Add(wSpot);
}
Refresh();
}
private string UpdateText(RichTextBox list, IEnumerable
<object> elems)
{
var result = "";
foreach (var elem in elems)
result += elem;
return result;
}
private void PauseBtnClick(object sender, EventArgs e) {
if (timerAgent.Enabled)
{
timerAgent.Stop();
pauseBtn.Text = "Play";
}
else
{
timerAgent.Start();
pauseBtn.Text = "Pause";
} } }

 

From this code we may notice that the visual application consists of a grid where we have included Play/Pause buttons and used a timer to control rover actions and execute them every second. 

 

In order to set up our Mars Rover Curiosity and world we would need to define a set of initial beliefs, a terrain for the rover, and a real terrain of Mars (Listing 20).

rover

Listing 20.  Setting Up the Mars Rover Curiosity and World

var water = new List<Tuple<int, int>>

{
new Tuple<int, int> (1, 2),
new Tuple<int, int> (3, 5),
};
var obstacles = new List<Tuple<int, int>> {
new Tuple<int, int> (2, 2),
new Tuple<int, int> (4, 5),
};
var beliefs = new List<Belief> {
new Belief(TypesBelief.PotentialWaterSpots, water), new Belief(TypesBelief.ObstaclesOnTerrain, obstacles),
};
var marsTerrain = new [,]
{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0.8, -1, 0, 0, 0, 0, 0, 0},
{0, 0, 0.8, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0},{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
var roverTerrain = new [,]
{
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0.8, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0.8, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
var mars = new Mars(marsTerrain);
var rover = new MarsRover(mars, roverTerrain, 7, 8, beliefs, 0.75, 2);
Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MarsWorld(rover, mars, 10, 10));

 

Once we run the application, a GUI like the one illustrated in Figurewill show up. In this program, one can easily differentiate water locations (water drops images) from obstacle locations (rocks images). 

 

Windows Forms application showing the rover, its SenseRadius, beliefs of water locations and obstacles marked as yellow squares, and actual water and obstacle locations without any yellow square surrounding them

 Mars Rover code

Notice the light-color cells surrounding the rover at all times; these are the cells that the rover can “see” or perceive at any given moment and are defined by the SenseRadius parameter (defined as a [Manhattan distance] value of 2 in the setup code) and the “discrete” circle whose radius is precisely the SenseRadius and whose center is the rover’s current location.

 

On the right side of the application we have a panel with various information sections, such as Beliefs, Desires, Intentions, WaterFoundAt. All of these are Windows Forms controls and ultimately use the ToString() overrides presented in the last section.

 

The time to see our Mars Rover Curiosity agent in action has come. Let’s see what happens when we run the application.

The rover creates a plan to go to location (3, 5), its closest probable water location, and so it creates a plan or sequence of actions (denoted in yellow cells) to get there and dig in. Notice that the plan (sequence of actions) or path returned by our path-finding algorithm is denoted in yellow with the purpose of making it easier for us to comprehend where the rover is going and why.

 

In this case, the rover is going after its closest water-location belief. Once it gets there, it discovers that its belief was wrong and there was no water in the pursued location as there was no obstacle in a cell adjacent to that water-location belief.

 

The good news is that while exploring that area the rover perceived a water location nearby (in its sensing circle) and so it adventures to go there to find out more.

 

The previous location sought by the agent is a water location, so the WaterFound data structure is updated, and the rover has found water on Mars! Afterward, it continues pursuing its next belief: water at (1, 2).

 

Once again when approaching (entering its perception or sense radius), the next water-location belief is discarded by the agent as well as another obstacle-location belief, and so the beliefs set is updated.

 Mars Rover code water

Now that the rover has exhausted its beliefs set it will wander around until our Statistics and Probability deliberative component is activated and causes the rover to inject itself with a new belief that is drawn from logical conclusions.

 

In this case—and imitating what our human mind would do, because we are merely trying to mimic what a human would do in this situation—we would think that it’s more likely, or that our chances of finding water are far greater, in an unexplored area. 

 

In the same way we can have a diversification stage to explore poorly visited or unexplored areas of the terrain we can also have an intensification stage to better explore areas where water has been previously found; that is, promising areas of the terrain. In our case the intensification phase could involve having the rover wander around in some sector of the terrain.

 

As we shall see in future blogs, finding a balance between the intensification and diversification stages (sometimes called the explore– exploit tradeoff) in search-related problems is essential, and most problems we face in our daily lives are search problems or optimization problems that in the end are search problems, as we search in the space of all possible solutions for one that is the best or optimal.

 

Thus, many problems can be reduced to merely searching, and this is a complicated task that typically requires cleverness.

 

Continuing with our Mars Rover Curiosity example, Figure shows the rover after it finishes its wandering-around stage and injects itself with a belief of water at the lower-left corner cell of the third sector, and so it sets course to reach that cell.

 

The injection of this belief allows the rover to find an actual water location that was in the vicinity of the injected water-location belief. Thus, by diversifying the search to unexplored areas we found an actual water location. This process is repeated again; the rover wanders around (random moves), eventually injects a new belief, and moves to that location.

 

The Mars Rover Curiosity presented in this blog has multiple features that can be refined to improve its performance. For instance, the WanderThreshold may be adjusted since the rover spends more and more time on Mars, looking to prolong the time it stays wandering in a certain area; this decision may be dependent on the square area of the sector where it’s wandering.

 

The strategy of always choosing a corner of the less-frequently visited sector to inject the water-location belief can also change and be made dependent on various conditions related to the rover’s history or state. 

mars_rover

The choice can also be made randomly; i.e., choose a random cell in the selected sector to inject the water-location belief or maybe choose the least-visited cell in that sector.

 

The division of the terrain may also change; we could use a set of division patterns collected in a database to divide the terrain in different ways (not always with 2n subdivisions) and give the rover the opportunity to explore different areas of diverse shapes.

The possibilities are endless, and it’s up to the reader to use the skeleton provided in this blog and create their perfect Mars Rover Curiosity.

 

Now that we have examined a complete practical problem of an agent and an agent’s architecture, we can move forward and explore multi-agent systems in which various agents coexist and maybe collaborate or compete to achieve certain goals that could be common to them all. This will be the main focus of the next blog.

 

Summary

Throughout this blog we presented the practical problem of designing a Mars Rover Curiosity AI using a hybrid architecture composed of a reactive layer and a deliberative layer that implements the BDI (Beliefs, Desires, and Intentions) paradigm.

 

The Mars Rover Curiosity example included a visual application (Windows Forms) that demonstrated how the rover reacts to different scenarios, how it’s able to plan via a path-finding algorithm, and how it’s able to provide timely responses to immediately perceived situations.

 

We also presented a Statistics and Probability component in the agent that acts as a deliberative component and allows it to explore unexplored or poorly visited areas of the terrain.

Recommend