Difference between revisions of "Getting Started with PyBioSim"
(Created page with "== Working with Simulator.py == When you first open Simulator.py, you will see something like this. <pre> ''' Created on Jan 16, 2015 @author: Arindam ''' import numpy as np...") |
|||
Line 371: | Line 371: | ||
</pre> | </pre> | ||
+ | |||
+ | == Using Brain's to define custom behaviors for agents == | ||
+ | You can define custom behaviors for agents by writing custom implementations for brains. | ||
+ | Here is the structure of the RunAtBallBrain.py | ||
+ | <pre> | ||
+ | from Agent import Agent | ||
+ | from Ball import Ball | ||
+ | from Obstacle import Obstacle | ||
+ | from LinearAlegebraUtils import getYPRFromVector | ||
+ | import numpy as np | ||
+ | from Action import Stun, Kick | ||
+ | class RunAtBallBrain(object): | ||
+ | ''' | ||
+ | classdocs | ||
+ | ''' | ||
+ | |||
+ | |||
+ | def __init__(self): | ||
+ | pass | ||
+ | |||
+ | def takeStep(self, myTeam=[], enemyTeam=[], balls=[], obstacles=[]): | ||
+ | actions = [] | ||
+ | deltaPos = np.array([1, 0, 0]) | ||
+ | deltaRot = getYPRFromVector(balls[0].position) | ||
+ | myTeamName = myTeam[0].team.name | ||
+ | for agent in enemyTeam: | ||
+ | if myTeamName == 'A': | ||
+ | actions.append(Stun(agent, 10)) | ||
+ | for ball in balls: | ||
+ | actions.append(Kick(ball, np.array([1, 0, 0]), 100)) | ||
+ | return deltaPos, deltaRot, actions | ||
+ | </pre> | ||
+ | |||
+ | The brain contains a takeStep() method which receives the following inputs: | ||
+ | myTeam = A list of agent objects which are in the same team as the agent | ||
+ | enemyTeam = A list of agent objects which are in the opposing team | ||
+ | balls = A list of attractor's or balls in the scene | ||
+ | obstacles = A lost of spherical obstacles which the agents should avoid | ||
+ | |||
+ | Each of the above elements contains a position represented in egocentric co-ordinate frame where the agent itself is the origin, forward of the agent is positive x, upwards is positive z, and right is positive y. | ||
+ | |||
+ | The brain returns three things: | ||
+ | deltaPos = a delta position, can be thought of as a direction vector | ||
+ | deltaRot = a delta rotation, represented in degrees in the order [yaw, pitch, roll] | ||
+ | actions = a list of actions the agent needs to perform, actions are objects of two classes, Kick and Stun | ||
+ | |||
+ | Syntax: | ||
+ | <pre> | ||
+ | Kick('ball to kick', 'egocentric vector of direction to kick in', 'kick intensity') | ||
+ | Stun('agent to stun', 'duration in seconds') | ||
+ | </pre> | ||
+ | |||
+ | 'ball to kick' and 'agent to stun' are the references to the objects passed in balls and enemyTeam. | ||
+ | |||
+ | In the above example we want team A to be able to stun agents in team B. | ||
+ | So, we loop over the enemy team list and add Stun objects to actions list for each agent in the enemyTeam list only if the team the agent is in, is team 'A'. | ||
+ | Similarly, we loop over all the 'balls' in the balls list and add a Kick object to the actions list for every ball in the list. |
Revision as of 19:52, 12 March 2015
Working with Simulator.py
When you first open Simulator.py, you will see something like this.
''' Created on Jan 16, 2015 @author: Arindam ''' import numpy as np import matplotlib.pyplot as plt from numpy import * from World import * from Agent import Agent from Obstacle import * from pylab import * import os from Ball import Ball from matplotlib import animation from LinearAlegebraUtils import distBetween from RunAtBallBrain import RunAtBallBrain from Team import Team from copy import deepcopy import time from FlockingBrain import FlockingBrain #Called once for initialization ''' Usage guidelines: 1. Define globals required for the simulation in the __init__ constructor, here we define a bunch of waypoints for the ball 2. Initialize the globals in the setup() method. ''' class Simulator(object): def __init__(self, world, simTime, fps, imageDirName): self.world = world self.simTime = simTime self.fps = fps self.imageDirName = imageDirName def setup(self): #called at a fixed 30fps always def fixedLoop(self): #Called at specifed fps def loop(self, ax): self.world.draw(ax) def run(self): #Run setup once self.setup() #Setup loop timeStep = 1/double(30) frameProb = double(self.fps) / 30 currTime = double(0) drawIndex = 0 physicsIndex = 0 while(currTime < self.simTime): self.fixedLoop() currProb = double(drawIndex)/double(physicsIndex+1) if currProb < frameProb: self.drawFrame(drawIndex) drawIndex+=1 physicsIndex+=1 currTime+=double(timeStep) print "Physics ran for "+str(physicsIndex)+" steps" print "Drawing ran for "+str(drawIndex)+" steps" def drawFrame(self, loopIndex): fig = plt.figure(figsize=(16,12)) ax = fig.add_subplot(111, projection='3d') ax.view_init(elev = 30) ax.set_xlabel("X") ax.set_ylabel("Y") ax.set_zlabel("Z") fname = self.imageDirName + '/' + str(int(100000000+loopIndex)) + '.jpg' # name the file self.loop(ax) plt.gca().set_ylim(ax.get_ylim()[::-1]) savefig(fname, format='jpg', bbox_inches='tight') print 'Written Frame No.'+ str(loopIndex)+' to '+ fname plt.close() #Simulation runs here #set the size of the world world = World(100, 100) #specify which world to simulate, total simulation time, and frammerate for video sim = Simulator(world, 60, 30, "images") #run the simulation sim.run() ''' To create a video using the image sequence, execute the following command in command line. >ffmpeg -f image2 -i "1%08d.jpg" -r 30 outPut.mp4 Make sure to set your current working directory to /images and have ffmpeg in your path. '''
The import methods to note here are the setup(), loop() and fixedLoop() methods. setup() : This method is called once before the simulation, we'll use this to initialize the world and setup various parameters. loop() : This method is called repeatedly at some specified frame rate(we'll see how to change this later, right now its set to 30), you can think of this as a draw loop, by default it calls world.draw() which draws the world. fixedLoop() : This method is called at 30 times a second fixed, always. This method should be used for moving objects in the world. This allows your movement to be asynchronous to the speed of the video.
Below is an example of how to use these methods to create a simulation of two teams chasing a moving ball. Using setup() to initialize the world: First Lets setup two teams in our setup() method.
teamA = Team("A", '#ff99ff') teamB = Team("B", '#ffcc99')
Syntax: Team("String Name of team", "Hex color code for team color")
Now lets create the agents that will be part of these teams. Here's a snippet on how to create an agent.
ag1Pos = array([80, 50, -20]) ag1Rot = array([30, 0, 0]) ag1Brain = RunAtBallBrain() agent1 = Agent(teamA, ag1Pos, ag1Rot, ag1Brain, 5, 5)
First we define the position where we want the agent to start. ag1Pos is a numpy array of the (x, y, x) co-ordinates. Second we define the rotation we want the agent to start with. ag1Rot is a numpy array of (yaw, pitch , roll) in degrees. We then initialize an instance of a "Brain" which defines the behavior of the agent. We'll see how to create a custom one later, right now you can use the provided RunAtBallBrain() which defines a behaviour in which the agent chases the ball. Lastly, we initialize an agent as an Object of the Agent class. Syntax : Agent( "team of Agent", "Starting Position", "Starting Rotation", "Brain", "Radius of Collision", "Draw Radius") To add this agent into the world we need to call:
self.world.agents.append(agent1)
Now lets define the ball which the agents will chase.
#define a ball ball = Ball(array([0, 0, 0])) #add the ball to the world self.world.balls.append(ball)
Similar to our agents we provide a numpy array as a position of our ball and we then add it to the world. Syntax: Ball( "position" )
Now for some decorative obstacles.
#define a bunch of obstacles ob1Pos = array([-50,-50,-50]) ob1 = Obstacle(ob1Pos, 30) ob2Pos = array([80,-50,-50]) ob2 = Obstacle(ob2Pos, 20) #add obstacles to the world self.world.obstacles.append(ob1); self.world.obstacles.append(ob2)
Just like the ball, we define two obstacles with a numpy array as a position, additionally we provide a radius for the obstacle. Syntax: Obstacle("position", "radius")
That's it, the world is setup, no we'll see how to move the agents and the ball around using fixedLoop(). Using fixedLoop() to move objects: Lets make sure we tell all our agents to move to do this we call,
for agent in self.world.agents: agent.moveAgent(self.world)
This uses the behavior defined in the agents brain to move them around. Now, to move the ball. The ball has a method call moveBall("position", "speed") which moves the ball to a certain position at a certain speed. We'll provide a series of points to this method to move it around. Heres the logic for it: 1. Maintain a list of waypoints 2. Move the ball with a certain speed to waypoint[0]. 3. If I'm really close to waypoint[0] then remove waypoint[0] from my lest of waypoints.(Python will conveniently reorder the list so waypoint[1] is now waypoint[0]) 4. Continue till waypoints.size > 0
Important: We need to maintain list of "waypoints" for the ball, we cannot do this inside fixedLoop() because it would be re-created at the start of every fixedLoop() call, thus we need to define it as a global. Instead of using python's global keyword, we define it as a member of our Simulator class inside the Simulator class's __init()__ constructor.
Here's the above logic in code form,
for ball in self.world.balls: if len(self.ballWPs) > 0: ball.moveBall(self.ballWPs[0], 1) if distBetween(ball.position, self.ballWPs[0]) < 0.5: if len(self.ballWPs) > 0: self.ballWPs.remove(self.ballWPs[0])
That,s it! Your Simulator.py should look like this:
''' Created on Jan 16, 2015 @author: Arindam ''' import numpy as np import matplotlib.pyplot as plt from numpy import * from World import * from Agent import Agent from Obstacle import * from pylab import * import os from Ball import Ball from matplotlib import animation from LinearAlegebraUtils import distBetween from RunAtBallBrain import RunAtBallBrain from Team import Team from copy import deepcopy import time from FlockingBrain import FlockingBrain #Called once for initialization ''' Usage guidelines: 1. Define globals required for the simulation in the __init__ constructor, here we define a bunch of waypoints for the ball 2. Initialize the globals in the setup() method. ''' class Simulator(object): def __init__(self, world, simTime, fps, imageDirName): self.world = world self.simTime = simTime self.fps = fps self.imageDirName = imageDirName self.currWP = 0 self.ballWPs = [array([50.0, -100.0, 0.0]), array([0.0, 100.0, -70.0]), array([50.0, 20.0, 100.0]),array([-30.0, 50.0, -100.0]), array([80.0, -50.0, 50.0]), array([80.0, -50.0, -50.0]), array([-65.0, 20.0, 50.0]), array([-50.0, 20.0, -60.0])] def setup(self): #setup directory to save the images self.imageDirName = 'images' try: os.mkdir(self.imageDirName) except: print self.imageDirName + " subdirectory already exists. OK." #define teams which the agents can be a part of teamA = Team("A", '#ff99ff') teamB = Team("B", '#ffcc99') #Defining a couple of agents ag1Pos = array([80, 50, -20]) ag1Rot = array([30, 0, 0]) ag1Brain = RunAtBallBrain() agent1 = Agent(teamA, ag1Pos, ag1Rot, ag1Brain, 5, 5) ag2Pos = array([-80, 0, 0]) ag2Rot = array([0, 0, 0]) ag2Brain = RunAtBallBrain() agent2 = Agent(teamA, ag2Pos, ag2Rot, ag2Brain, 5, 5) ag3Pos = array([70, 30, 50]) ag3Rot = array([0, 0, 0]) ag3Brain = RunAtBallBrain() agent3 = Agent(teamB, ag3Pos, ag3Rot, ag3Brain, 5, 5) ag4Pos = array([-80, 20, 60]) ag4Rot = array([0, 0, 0]) ag4Brain = RunAtBallBrain() agent4 = Agent(teamB, ag4Pos, ag4Rot, ag4Brain, 5, 5) #Add the agent to the world self.world.agents.append(agent1) self.world.agents.append(agent2) self.world.agents.append(agent3) self.world.agents.append(agent4) # #define a bunch of obstacles ob1Pos = array([-50,-50,-50]) ob1 = Obstacle(ob1Pos, 30) ob2Pos = array([80,-50,-50]) ob2 = Obstacle(ob2Pos, 20) #add obstacles to the world self.world.obstacles.append(ob1); self.world.obstacles.append(ob2) #define a ball ball = Ball(array([0, 0, 0])) #add the ball to the world self.world.balls.append(ball) #called at a fixed 30fps always def fixedLoop(self): for agent in self.world.agents: agent.moveAgent(self.world) for ball in self.world.balls: if len(self.ballWPs) > 0: ball.moveBall(self.ballWPs[0], 1) if distBetween(ball.position, self.ballWPs[0]) < 0.5: if len(self.ballWPs) > 0: self.ballWPs.remove(self.ballWPs[0]) #Called at specifed fps def loop(self, ax): self.world.draw(ax) def run(self): #Run setup once self.setup() #Setup loop timeStep = 1/double(30) frameProb = double(self.fps) / 30 currTime = double(0) drawIndex = 0 physicsIndex = 0 while(currTime < self.simTime): self.fixedLoop() currProb = double(drawIndex)/double(physicsIndex+1) if currProb < frameProb: self.drawFrame(drawIndex) drawIndex+=1 physicsIndex+=1 currTime+=double(timeStep) print "Physics ran for "+str(physicsIndex)+" steps" print "Drawing ran for "+str(drawIndex)+" steps" def drawFrame(self, loopIndex): fig = plt.figure(figsize=(16,12)) ax = fig.add_subplot(111, projection='3d') ax.view_init(elev = 30) ax.set_xlabel("X") ax.set_ylabel("Y") ax.set_zlabel("Z") fname = self.imageDirName + '/' + str(int(100000000+loopIndex)) + '.jpg' # name the file self.loop(ax) plt.gca().set_ylim(ax.get_ylim()[::-1]) savefig(fname, format='jpg', bbox_inches='tight') print 'Written Frame No.'+ str(loopIndex)+' to '+ fname plt.close() #Simulation runs here #set the size of the world world = World(100, 100) #specify which world to simulate, total simulation time, and frammerate for video sim = Simulator(world, 60, 30, "images") #run the simulation sim.run() ''' To create a video using the image sequence, execute the following command in command line. >ffmpeg -f image2 -i "1%08d.jpg" -r 30 outPut.mp4 Make sure to set your current working directory to /images and have ffmpeg in your path. '''
Using Brain's to define custom behaviors for agents
You can define custom behaviors for agents by writing custom implementations for brains. Here is the structure of the RunAtBallBrain.py
from Agent import Agent from Ball import Ball from Obstacle import Obstacle from LinearAlegebraUtils import getYPRFromVector import numpy as np from Action import Stun, Kick class RunAtBallBrain(object): ''' classdocs ''' def __init__(self): pass def takeStep(self, myTeam=[], enemyTeam=[], balls=[], obstacles=[]): actions = [] deltaPos = np.array([1, 0, 0]) deltaRot = getYPRFromVector(balls[0].position) myTeamName = myTeam[0].team.name for agent in enemyTeam: if myTeamName == 'A': actions.append(Stun(agent, 10)) for ball in balls: actions.append(Kick(ball, np.array([1, 0, 0]), 100)) return deltaPos, deltaRot, actions
The brain contains a takeStep() method which receives the following inputs: myTeam = A list of agent objects which are in the same team as the agent enemyTeam = A list of agent objects which are in the opposing team balls = A list of attractor's or balls in the scene obstacles = A lost of spherical obstacles which the agents should avoid
Each of the above elements contains a position represented in egocentric co-ordinate frame where the agent itself is the origin, forward of the agent is positive x, upwards is positive z, and right is positive y.
The brain returns three things: deltaPos = a delta position, can be thought of as a direction vector deltaRot = a delta rotation, represented in degrees in the order [yaw, pitch, roll] actions = a list of actions the agent needs to perform, actions are objects of two classes, Kick and Stun
Syntax:
Kick('ball to kick', 'egocentric vector of direction to kick in', 'kick intensity') Stun('agent to stun', 'duration in seconds')
'ball to kick' and 'agent to stun' are the references to the objects passed in balls and enemyTeam.
In the above example we want team A to be able to stun agents in team B. So, we loop over the enemy team list and add Stun objects to actions list for each agent in the enemyTeam list only if the team the agent is in, is team 'A'. Similarly, we loop over all the 'balls' in the balls list and add a Kick object to the actions list for every ball in the list.