Quick Links
Advanced Section
Learn how to take your simulation to the next step using Traversecraft with this tutorial.
We will try to create a simulation that resembles this.
Import required modules¶
Let's start by importing the required module form the main library.
It's always a good idea to check whether the library is installed or not. The below command will install the library if the library is not already installed.
!pip install -i https://test.pypi.org/simple/ TraverseCraft
Looking in indexes: https://test.pypi.org/simple/ Requirement already satisfied: TraverseCraft in /media/srajan/Data/Project/LibraryTests/envs/lib/python3.8/site-packages (0.9.5) Requirement already satisfied: prettytable in /media/srajan/Data/Project/LibraryTests/envs/lib/python3.8/site-packages (from TraverseCraft) (0.7.3.dev122)
Let's import Graph World and Graph Agent¶
we will import the CreateGraphWorld
class from the world
module and GraphAgent
class from the agent
module.
Note: It is very important to call the right agent for the given world, otherwise the module will through an error. Each world behave differently and each agent is tailored for that world only.
Remark: Don't worry if during the importing the library prints the OS Type. Actually the library uses some internal variables which are OS depended. It is necessary that the os type printed and your OS type matches. If it does't match please report this.
from traverseCraft.world import CreateGraphWorld
from traverseCraft.agent import GraphAgent
OS Type: Linux
Graph World¶
Let's start with creating a Graph world using the image below. But this time we will not use the default settings and will customize our world.
World Information¶
Before creating the Graph World we have to first create the world blueprint/template. We call this blueprint/template as world information. This is like a abstract view of your world.
The world information will be a dictionary with keys and values as the following:
adj
: a dictionary of adjacency list containing each node ID as key and value as a list of ID's of neighbors.position
: a dictionary containing each node ID as key and value as a tuple containing position of the node on the world.goals
: a list containing node IDs.
These are the mandatory keys, which have to be present in the world information.
Note:
- You only have to pass node IDs which can be a
char
,str
,int
and the library will use it's internal data nodes to create the Graph along with world creation. You can access each node and Graph information from the library itself. This will also be helpful if you further want to modify/customize the nodes. The keys mention above in the world information are only the necessary key's but there are other keys as well.- The position of each node will be the coordinates. The (0,0) coordinate is you screens top left corner. Please consider drawing the world on a paper before actually writing the world abstract information.
- Please refer the API Reference section for more information.
# tree world information
graphWorldInfo = {
'adj': {
'A': ['B', 'C', 'J'],
'B': [],
'C': ['D', 'I', 'K'],
'D': ['K'],
'E': ['F'],
'F': ['E', 'H'],
'G': ['E'],
'H': ['G'],
'I': ['J'],
'J': ['A'],
'K': ['I']
},
'position': {
'A': (200, 100),
'B': (100, 200),
'C': (300, 200),
'D': (200, 300),
'E': (700, 100),
'F': (600, 200),
'G': (800, 200),
'H': (700, 300),
'I': (500, 200),
'J': (400, 100),
'K': (400, 300)
},
'goals': ['B', 'G']
}
Note: other then the
adj
,position
, andgoals
. We also haveedges
, which is a dictionary of edge value(Default each edge is assigned a value of 1) andvals
, which is a dictionary of node value(Default each node is assigned the value same as its ID).
Create a Graph World¶
Let's start with creating a Graph world using the image below. But this time we will not use the default settings and will customize our world. We will make the following changes:
radius
: Controls the size of each node of the Graph.nodeColor
: Set's the color of all the nodes where the agent can move.goalColor
: Set the color of all the goal states.width
: The windows width.height
: The windows height.
we will set the following information:
- Radius: 25
- Node: green color
- Goal: gray color
- Width: 900
- Height: 500
# First let's Create a graph world object!
graphWorld = CreateGraphWorld(worldName="Custom Graph World", worldInfo=graphWorldInfo, radius=25, nodeColor='green', goalColor="gray", width=900, height=500)
Compile our Graph World¶
Since, we are sure that we don't want to do any more changes in the world, thus we will compile our world.
Note: Once you compile the world you can't change the structure of the world. So make sure that you do all the changes before compiling the world.
graphWorld.constructWorld()
More About Graph Node¶
You may be wondering how the library is managing the Graph data structure, or how can you modify the Graph node.
The library provide a way to get the pointer of each node Id. You can use the nodeMap
public variable, which is a dictionary with the keys as the node ID and value as the pointer.
Note: The library have a
dataStructures
module which contain theGraphNode
class. You can use this class to modify further according to your needs.
Let's see information of some of the Graph Nodes.¶
To get a general information of any Graph node we can simple use the print()
statement.
# Normal node
normalNode = graphWorld.nodeMap['C']
print("Information of node C")
print(normalNode)
print("-"*30)
# Goal node
goalNode = graphWorld.nodeMap['G']
print("Information of node G")
print(goalNode)
Information of node C Graph Node Id: C Neighbors: ['D', 'I', 'K'] Value: C Goal State: False Edges: [1, 1, 1] Heat Map Value: 0 ------------------------------ Information of node G Graph Node Id: G Neighbors: ['E'] Value: G Goal State: True Edges: [1] Heat Map Value: 0
Note: Here, you can see what information the
GraphNode
stores. exceptHeat Map Value
other information are self explanatory, theHeat Map Value
actually stores the number of time the node is been visited by any agent.
Let's see some basic information about our Graph World!¶
you can use a simple print()
statement or you can use the builtin function aboutWorld()
to get the world information as a string.
print(graphWorld)
+-------------------------+--------------------+ | Attribute | Value | +-------------------------+--------------------+ | World Name | Custom Graph World | | Goal Nodes | ['B', 'G'] | | Width | 900 | | Height | 500 | | Node Radius | 25 | | Font Size | 12 | | Font Bold | True | | Font Italic | True | | Node Color | green | | Goal Color | gray | | Line Thickness | 2 | | Arrow Shape | (10, 12, 5) | | Button Background Color | #7FC7D9 | | Button Foreground Color | #332941 | | Text Font | Helvetica | | Text Size | 24 | | Text Weight | bold | | Button Text | Start Agent | +-------------------------+--------------------+
help(graphWorld)
Help on CreateGraphWorld in module traverseCraft.world object: class CreateGraphWorld(builtins.object) | CreateGraphWorld(worldName: str, worldInfo: dict, radius: int = 20, fontSize: int = 12, fontBold: bool = True, fontItalic: bool = True, nodeColor: str = 'gray', goalColor: str = 'green', width: int = 3286, height: int = 1080, lineThickness: int = 2, arrowShape: tuple = (10, 12, 5), buttonBgColor: str = '#7FC7D9', buttonFgColor: str = '#332941', textFont: str = 'Helvetica', textSize: int = 24, textWeight: str = 'bold', buttonText: str = 'Start Agent', logoPath: str = None) | | Class representing a graph world. | | | Parameters: | - worldName (str): The name of the world. | - _worldInfo (dict): A dictionary containing information about the world. | - 'goals' (list): List of goal node IDs. | - 'adj' (dict): Adjacency list representing graph connections. | - 'position' (dict): Dictionary of node positions with node IDs as keys. | - 'edges' (dict, optional): Dictionary of edge value. Default each edge is assigned a value of 1. | - 'vals' (dict, optional): Dictionary of node values. Default each node is assigned the value same as its ID. | - radius (int): The radius of the nodes in the world visualization. Default is 20. | - fontSize (int): The font size of the node labels. Default is 12. | - fontBold (bool): Whether to use bold font for the node labels. Default is True. | - fontItalic (bool): Whether to use italic font for the node labels. Default is True. | - nodeColor (str): The color of the nodes. Default is "gray". | - goalColor (str): The color of the goal nodes. Default is "green". | - width (int): The width of the world visualization canvas. Default is SCREEN_WIDTH. | - height (int): The height of the world visualization canvas. Default is SCREEN_HEIGHT. | - lineThickness (int): The thickness of the lines connecting the nodes. Default is 2. | - arrowShape (tuple): The shape of the arrows indicating the direction of the edges. Default is (10, 12, 5). | - buttonBgColor (str): The background color of the buttons. Default is "#7FC7D9". | - buttonFgColor (str): The foreground color of the buttons. Default is "#332941". | - textFont (str): The font family of the button text. Default is "Helvetica". | - textSize (int): The font size of the button text. Default is 24. | - textWeight (str): The font weight of the button text. Default is "bold". | - buttonText (str): The text displayed on the buttons. Default is "Start Agent". | - logoPath (str, optional): The file path to the logo image. Default is traverseCraft logo. | | Attributes: | - worldID (str): Class identifier for the graph world. | - _worldName (str): The name of the world. | - _worldInfo (dict): Dictionary containing the world's information. | - _graphRootId (str): The ID of the root node. | - _goalIds (list): List of goal node IDs. | - _position (dict): Dictionary of node positions. | - _width (int): The width of the visualization canvas. | - _height (int): The height of the visualization canvas. | - _radius (int): The radius of the nodes. | - _nodeColor (str): The color of the nodes. | - _goalColor (str): The color of the goal nodes. | - _fontSize (int): The font size of the node labels. | - _fontBold (bool): Whether the node labels are bold. | - _fontItalic (bool): Whether the node labels are italic. | - _lineThickness (int): The thickness of the lines connecting the nodes. | - _arrowShape (tuple): The shape of the arrows indicating the direction of the edges. | - _logoPath (str): The file path to the logo image. | - _root (Tk): The root Tkinter object. | - _canvas (Canvas): The canvas object for drawing the world. | - nodeMap (dict): Dictionary mapping node IDs to canvas objects. | - _visited (dict): Dictionary tracking visited nodes. | - _agent: The agent in the world. | - _nodeObj (dict): Dictionary mapping node IDs to node objects. | - _nodeTextObj (dict): Dictionary mapping node IDs to node label objects. | - _buttonBgColor (str): The background color of the buttons. | - _buttonFgColor (str): The foreground color of the buttons. | - _buttonText (str): The text displayed on the buttons. | - _textFont (str): The font family of the button text. | - _textSize (int): The font size of the button text. | - _textWeight (str): The font weight of the button text. | | Methods defined here: | | __init__(self, worldName: str, worldInfo: dict, radius: int = 20, fontSize: int = 12, fontBold: bool = True, fontItalic: bool = True, nodeColor: str = 'gray', goalColor: str = 'green', width: int = 3286, height: int = 1080, lineThickness: int = 2, arrowShape: tuple = (10, 12, 5), buttonBgColor: str = '#7FC7D9', buttonFgColor: str = '#332941', textFont: str = 'Helvetica', textSize: int = 24, textWeight: str = 'bold', buttonText: str = 'Start Agent', logoPath: str = None) | Initializes the Graph World. | | __str__(self) | Describes the attributes of the world. | | Parameters: | None | | Returns: | str: The attributes of the world. | | aboutWorld(self) | Describes the attributes of the world. | | Parameters: | None | Returns: | str: The attributes of the world. | | changeNodeColor(self, nodeId, color) | Changes the color of a node in the graph. | | Args: | nodeId (int): The ID of the node to change the color of. | color (str): The new color to set for the node. | | Returns: | None | | changeNodeText(self, nodeId, newText) | Changes the text of a node in the graph. | | Parameters: | nodeId (int): The ID of the node to change the text of. | newText (str): The new text to set for the node. | | Returns: | | constructWorld(self) | Constructs the graph world. | | Parameters: | self (World): The World instance. | | Returns: | None | | getNode(self, nodeId) | Returns the pointer to the node with the given nodeId. | | Parameters: | nodeId: The ID of the node to retrieve the pointer for. | | Returns: | Node: The pointer to the node with the given nodeId. | | setAgent(self, agent) | Set the agent for the world. | | Parameters: | agent (Agent): The agent to be set. | | Returns: | None | | showWorld(self) | Displays the world. | | Parameters: | None | Returns: | None | | summary(self) | Generates a summary of the world. | | Parameters: | None | Returns: | str: The summary of the world. | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | worldID = 'GRAPHWORLD'
Let's create our agent which will interact with the world.¶
We will create a graph agent with the following custom settings.
agentColor
: set the color of the node where the agent is present.heatMapView
: set True or False to decide whether to see the heatmap in realtime in simulation or not.
we will set the values to the following:
- agentColor: aqua
- heatMapView: False(to only see the simulation)
graphAgent = GraphAgent(agentName="Graph Agent", world=graphWorld, agentColor="aqua", heatMapView=False)
Let's Set the Start State¶
Unlike from the tree world, here the agent can select any node as it's start state. By default the library chooses the first node in the nodeMap
as the start state, but it will be a problem every time we run the simulation as we are not sure which will be the start state. Thus it's always recommended to set the start state. We will use the setStartState()
method of the agent class to set the start state of the agent.
graphAgent.setStartState('C')
Let's see some basic information about our Graph Agent.¶
you can use a simple print()
statement or you can use the builtin function aboutAgent()
to get the agent information as a string.
print(graphAgent)
+---------------+--------------------+ | Attribute | Value | +---------------+--------------------+ | Agent Name | Graph Agent | | Agent Color | aqua | | World Name | Custom Graph World | | World ID | GRAPHWORLD | | Start Node ID | C | +---------------+--------------------+
Connect Our Agent with the World¶
Now, we have our world ready and constructed and we also have our agent ready, but the world doesn't know the agent and the agent does't know the world. We have to connect the agent with the world. We will use the .setAgent()
method of the world to connect the agent with the world.
graphWorld.setAgent(graphAgent)
Algorithm¶
Now, we have connected the agent with the world, but we did not told the agent what to do in the world.
The agent have a method setAlgorithmCallBack()
which takes a function as a argument.
This function will be run during the simulation.
Note: Make sure that the function you are passing in the
setAlgorithmCallBack()
method does not take any argument.
Let's first create our Algorithm that will move the agent.¶
We will write a simple DFS algorithm to move our agent, and our main goal will be to get to any goal state.
Some Useful methods¶
checkGoalState()
method is use to check whether the given node ID is a goal state or not.moveAgent()
this method will return False if it is not possible to move the agent to the given node ID or will move the agent and return True if it is possible. We can also control the delay in the movement using thedelay
parameter.
DFS Algorithm¶
def dfs(nodeId, agent, visited=[]):
if agent.checkGoalState(nodeId):
return True
visited.append(nodeId)
for neighbor in graphWorld.nodeMap[nodeId].neighbors:
if neighbor.id in visited:
continue
agent.moveAgent(neighbor.id)
if dfs(neighbor.id, agent, visited):
return True
agent.moveAgent(nodeId)
return False
We will use the dfs Algorithm in the main callback function¶
Remember: Since the call back function will only accept a function with no arguments, thus it is always a good idea to create another function which takes the arguments and use that function in the call back function.
def toDo():
print("Agent Started moving around the world!")
if(dfs('C', graphAgent)):
print("Agent reached the goal state!")
else:
print("Agent could not reach the goal state!")
We are ready with our algorithm¶
Lets set the algorithm to tell the agent what to do.
graphAgent.setAlgorithmCallBack(toDo)
Display the world and Start the Simulation!¶
Now, we are all set. We will first show the world using the showWorld()
method and then on the world there is a button at the bottom to start the simulation.
Warning: Always make sure that the method
showWorld()
is called when you are ready to simulate, because after this no change can be made on the world or the agent.
graphWorld.showWorld()
Agent Started moving around the world! Agent reached the goal state!
In the window You can see that, this time no heatmap is forming where the agent is going. This happened because we switch off this feature also by simple changing the value of heatMapView
to False
during the tree agent object creation.
You can also see there is a slight delay in the movement of the agent, this is also a feature of agent, you can control the delay while calling the moveAgent
method by the parameter delay
.
Let's see the summary¶
The agent and the world keep some records, that we can generate and see.
The Agent have information about the time taken to run the whole simulation.¶
We can see this information using the summary
method.
print(graphAgent.summary())
+--------------+---------------------------+ | Attribute | Value | +--------------+---------------------------+ | Start Time | Thu, 20 Jun 2024 07:33:38 | | End Time | Thu, 20 Jun 2024 07:33:44 | | Elapsed Time | 6.018 sec | +--------------+---------------------------+
The World have information about the visited count after the whole simulation.¶
We can see this information using the summary
method.
print(graphWorld.summary())
+---------+------------------+ | Node ID | Number of Visits | +---------+------------------+ | B | 1 | | J | 1 | | I | 1 | | K | 1 | | D | 1 | | C | 1 | | A | 1 | | G | 0 | | H | 0 | | F | 0 | | E | 0 | +---------+------------------+
help(graphAgent)
Help on GraphAgent in module traverseCraft.agent object: class GraphAgent(builtins.object) | GraphAgent(world, agentName: str, agentColor: str = 'blue', startNodeId=None, heatMapView: bool = True, heatMapColor: str = '#FFA732', heatGradient: float = 0.05) | | The Graph Agent class. | | Parameters: | world (CreateGridWorld): The world object that the agent belongs to. | agentName (str): The name of the agent. | agentColor (str, optional): The color of the agent. Defaults to "blue". | heatMapView (bool, optional): Flag indicating whether to enable heat map view. Defaults to True. | heatMapColor (str, optional): The color of the heat map. Defaults to "#FFA732". | heatGradient (float, optional): The gradient of the heat map. Defaults to 0.05. | | Attributes: | _worldObj (CreateGraphWorld): The graph world object. | _worldID (str): The ID of the world. | _root (Tk): The root Tkinter object from the graph world. | _agentName (str): The name of the agent. | _agentColor (str): The color of the agent. | _heatMapView (bool): Indicates if the heat map view is enabled. | _heatMapColor (str): The color of the heat map. | _heatMapBaseColor (str): The base color of the heat map. | _heatGradient (float): The gradient value for the heat map. | _currentNode (GraphNode): The current node the agent is on. | _startState (GraphNode): The start node of the graph. | algorithmCallBack (function): Callback function for the agent's algorithm. | _startTime (float): The start time of the agent. | _endTime (float): The end time of the agent. | | Methods defined here: | | __init__(self, world, agentName: str, agentColor: str = 'blue', startNodeId=None, heatMapView: bool = True, heatMapColor: str = '#FFA732', heatGradient: float = 0.05) | Initialize self. See help(type(self)) for accurate signature. | | __str__(self) | Describes the attributes of the world. | | Parameters: | None | | Returns: | function(): The attributes of the world. | | aboutAgent(self) | Prints information about the agent. | | Parameters: | None | | Returns: | str: A string containing information about the agent. | | checkGoalState(self, nodeId) | Check if the given node is a goal state. | | Parameters: | node: The ID of the node to be checked. | | Returns: | bool: True if the node is a goal state, False otherwise. | | getHeatMapColor(self, value: float) | Returns the color for a given value on a heat map. | | Parameters: | value (float): The value to map to a color on the heat map. | | Returns: | tuple: The RGB color value for the given value on the heat map. | | moveAgent(self, nodeId, delay: int = 1) | Moves the agent to the specified node. | | Args: | nodeId: The ID of the node to which the agent should be moved. | delay (optional): The delay (in seconds) before moving to the next node. Default is 1 second. | | Returns: | bool: True if the agent was successfully moved to the node, False otherwise. | | runAlgorithm(self) | Executes the algorithm callback function. | | Raises: | ValueError: If the algorithm callback function is not set. | | setAlgorithmCallBack(self, algorithmCallBack) | Set the callback function for the algorithm. | | Parameters: | algorithmCallBack (function): The callback function to be set. | | Returns: | None | | setStartState(self, nodeId) | Sets the start state of the agent to the specified node. | | Args: | nodeId: The ID of the node to set as the start state. | | Raises: | ValueError: If the specified node ID is invalid. | | summary(self) | Returns a summary of the agent run. | | Parameters: | None | Returns: | str: A summary of the agent run. | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
This covers all the required features and knowledge required to create simulations for Graph World.
Next, please refer the API Reference section for more details regarding each class and methods.
Thanks!