Module simulationEnvironment
[hide private]
[frames] | no frames]

Source Code for Module simulationEnvironment

  1  ################################################################################## 
  2  # Copyright (c) 2011, Daniel Urieli, Peter Stone 
  3  # University of Texas at Austin 
  4  # All right reserved 
  5  #  
  6  # Based On: 
  7  #  
  8  # Copyright (c) 2000-2003, Jelle Kok, University of Amsterdam 
  9  # All rights reserved. 
 10  #  
 11  # Redistribution and use in source and binary forms, with or without 
 12  # modification, are permitted provided that the following conditions are met: 
 13  #  
 14  # 1. Redistributions of source code must retain the above copyright notice, this 
 15  # list of conditions and the following disclaimer. 
 16  #  
 17  # 2. Redistributions in binary form must reproduce the above copyright notice, 
 18  # this list of conditions and the following disclaimer in the documentation 
 19  # and/or other materials provided with the distribution. 
 20  #  
 21  # 3. Neither the name of the University of Amsterdam nor the names of its 
 22  # contributors may be used to endorse or promote products derived from this 
 23  # software without specific prior written permission. 
 24  #  
 25  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 26  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 27  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 28  # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 29  # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 30  # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 31  # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 32  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 33  # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 34  # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 35  ################################################################################## 
 36   
 37   
 38   
 39  from math import * 
 40  import numpy 
 41  import random 
 42   
43 -class SimulationEnvironment:
44 """ 45 46 Based on AIMA's environment: 47 Abstract class representing an Environment. 48 'Real' Environment classes inherit from this. 49 Your Environment will typically need to implement: 50 percept: Define the percept that an agent sees. 51 executeAction: Define the effects of executing an action. 52 53 """ 54
55 - def __init__( self, initialState, shipAgents ):
56 """ 57 Initialize the simulation environment. 58 59 @type initialState: State 60 @param initialState: The initial state of the sea and ships 61 62 @type shipAgents: array of Agents 63 @param shipAgents: The initial state of the sea and ships 64 """ 65 self.state = initialState 66 self.shipAgents = shipAgents 67 # initializing the callbacks for processing messages 68 self.msgCallbacks = {} 69 self.msgCallbacks['visit'] = self.processMsgVisit.__name__ 70 self.msgCallbacks['edgeLength'] = self.processMsgEdgeLength.__name__ 71 # . 72 # . 73 # . 74 # continue adding callbacks 75 76 # different data fields that gather some data about the simulation 77 self.time = 0 # simulation time: 1, 2, ... (logically, 0 is the initial state so couting from 1) 78 self.visitTimes = {} # maps a point to a list of times it has been visited 79 self.edgeLengths = {} # maps an edge to a list of measured edge travel-times
80 81 82
83 - def percept(self, agentIndex):
84 """ 85 Return the percept that the agent sees at this point. 86 We view the world such that the complete state is sent to 87 a ship, and a ship can only extract from it what it can percept. 88 """ 89 return self.state.ships[agentIndex].getPercepts(self.state)
90
91 - def executeAction(self, agentIndex, action):
92 """Change the world to reflect this action. 93 Here, we only change the ship's internal state. 94 The external state will be changed in exogenousChange() 95 96 @type agentIndex: int 97 @param agentIndex: sort of an agent-id - an agent's index in the array of all agents 98 99 @type action: CompositeAction 100 @param action: An action chosen by the agent, to be executed on the ship 101 """ 102 self.state.ships[agentIndex].executeAction(action)
103
104 - def exogenousChange(self):
105 """ 106 Compute a change in the world state, that is external to the agent. 107 This function is called in every simulation step, after computing the 108 effects of the agent's actions. 109 110 Here the change that is computed is: 111 - A change in the ships' external states, which are outside the control 112 of the agent. (Ships' internal states are changed by agent actions.) 113 - A change in the sea conditions 114 """ 115 # 2 steps: ship state change, and then 116 # enviroment change as a result of time 117 for i in range(len(self.state.ships)): 118 self.updateShipExternalState(i) 119 120 # TODO: put more reasonable value for time(?) 121 self.computeChangeInSeaConditions(timePassed=1)
122
123 - def step(self):
124 """ 125 Run the SimulationEnvironment for one time step (One second). 126 127 Clarification, originally from the AIMA book's code: 128 If the actions and exogenous changes are independent, 129 this method will do. 130 If there are interactions between them, you'll need to 131 override this method. 132 """ 133 self.time += 1 134 135 if not self.isDone(): 136 actions = [agent.getAction(self.percept(agentIndex)) for agentIndex, agent in enumerate(self.shipAgents)] 137 for (agentIndex, action) in enumerate(actions): 138 self.executeAction(agentIndex, action) 139 self.exogenousChange() 140 141 # TODO: currently messages are not part of an "action" - 142 # they are a kind of "metadata" that is being communicated. 143 # If needed, this could be changed in the future. 144 self.processAgentsMessages() 145 146 # ORIGINAL CODE 147 ''' 148 if not self.isDone(): 149 actions = [agent.getAction(self.percept(agent)) for agent in self.shipAgents] 150 for (agentIndex, action) in enumerate(actions): 151 self.executeAction(agentIndex, action) 152 self.exogenousChange() 153 '''
154 155
156 - def isDone(self):
157 "By default, we're done when we can't find a live agent." 158 # for agent in self.agents: 159 # if agent.is_alive(): return False 160 # return True 161 return False
162
163 - def run(self, steps=250000):
164 """Run the SimulationEnvironment for given number of time steps.""" 165 for step in xrange(steps): # xrange is a generator, more efficient 166 if self.isDone(): 167 return 168 self.step() 169 print 'Done!'
170
171 - def updateShipExternalState(self, shipIndex):
172 """ 173 Compute ship position change in time, given the current state. 174 175 Computation of the next state is based on the world state, 176 the ship internal and external state and time passed. 177 178 Currently, we approximate ship movement using the following model: 179 - For forward motion, we model forward force that operates on the ship 180 by the engine, and a drag, which is quadratic in the ship's speed. 181 - For turning, there is an rotational torque that is applied by the rudder, 182 and is proportional to the ship's speed, and to sin(rudder-steering-angle), 183 which is the projection of the rudder on the lateral direction. 184 There is also a rotational drag force, that is quadratic in the ship's 185 angular speed, (need to check about the accuracy of this modelling). 186 - Based on the above forces and the ship's mass, we compute the 187 forward and angular accelerations. 188 - Then based on the average forward and angular speed in a given time step, 189 computed using the above accelerations, we infer the turn radius, 190 and based on that, compute the ship position at the end of this time-step. 191 192 Approximations we make: 193 194 - Although the angular acceleration depends on forward speed, 195 which is changing due to forward acceleration, we still 196 assume constant angular acceleration, based on the avg. speed in this step. 197 - Forward acceleration computation does not take into account the affects 198 of turning which (might?) slow it down. 199 - Forward acceleration depends on the drag, which is changing with speed change, 200 but we approximate the drag based on the initial speed of a time step 201 202 """ 203 204 # TODO: later stage: collisions? 205 206 # Get the needed data 207 ship = self.state.ships[shipIndex] 208 s = self.state.shipExternalStates[shipIndex] 209 shipSpeed = s.speed 210 angularSpeed = s.angularSpeed 211 timeStepLength = 1 # TODO: change that in the future 212 213 214 # Compute the front acceleration and avg speed 215 forwardForce = ship.getForwardForce(shipSpeed) 216 forwardAcceleration = ship.getForwardAccelerationFromForce(forwardForce) 217 # print 'forwardAcceleration', forwardAcceleration 218 # v = v0 + at => avg = (v0 + v) / 2 = (v0 + v0 + at) / 2 = v0 + at/2 219 avgSpeed = shipSpeed + forwardAcceleration * timeStepLength * 0.5 220 # print 'avgSpeed', avgSpeed 221 222 # Compute the angular acceleration and avg speed 223 steeringForce = ship.getSteeringForce(avgSpeed, angularSpeed) 224 angularAcceleration = ship.getAngularAccelerationFromForce(steeringForce) 225 # print 'angularAcceleration', angularAcceleration 226 avgAngularSpeed = angularSpeed + angularAcceleration * timeStepLength * 0.5 227 # print 'avgAngularSpeed', avgAngularSpeed 228 avgAngularSpeedRad = radians(avgAngularSpeed) 229 # print 'avgAngularSpeedRad', avgAngularSpeedRad 230 231 232 # Compute the new position at the end of this time step 233 # TODO: This is an approximation. We compute the avg speed and the avg angular 234 # speed, and using a geometric computation for motion in constant 235 # forward / angular speeds. 236 237 if avgAngularSpeedRad == 0: # going straight 238 relXTranslation = avgSpeed * timeStepLength 239 relYTranslation = 0 240 else: 241 radius = float(avgSpeed) / avgAngularSpeedRad # the 0.0 is for floating point division 242 relXTranslation = radius * sin(avgAngularSpeedRad * timeStepLength) # yes, it's sinus... 243 relYTranslation = radius * (1 - cos(avgAngularSpeedRad * timeStepLength)) # 244 # A fix for turning in negative direction 245 if avgAngularSpeedRad < 0: 246 relYTranslation = -relYTranslation 247 # print 'relXTranslation', relXTranslation, 'relYTranslation', relYTranslation 248 249 startAngle = s.orientation 250 # print 'startAngle', startAngle 251 cos_start_angle = cos(radians(startAngle)) 252 sin_start_angle = sin(radians(startAngle)) 253 frontDirectionVector = numpy.array( [cos_start_angle , sin_start_angle] ) 254 # print 'frontDirectionVector', frontDirectionVector 255 sideDirectionVector = numpy.array( [-sin_start_angle, cos_start_angle] ) 256 # print 'sideDirectionVector', sideDirectionVector 257 shipPos = numpy.array( [s.x, s.y] ) 258 # print 'shipPos', shipPos 259 shipNewPos = shipPos + relXTranslation * frontDirectionVector + relYTranslation * sideDirectionVector 260 # print 'shipNewPos', shipNewPos 261 262 # Offset caused by environment conditions 263 sea = self.state.sea 264 shipNewPos += ship.getOffsetByEnvConditions(sea.wind.getSpeedVectorInLocation(s.x, s.y), 265 sea.waterCurrents.getSpeedVectorInLocation(s.x, s.y), 266 startAngle) 267 # TODO: for DEBUG 268 progressVector = shipNewPos - shipPos 269 # print 'progressAngle', degrees(atan2(progressVector[1], progressVector[0])) 270 271 s.x = shipNewPos[0] #+ random.gauss(0, 2) 272 s.y = shipNewPos[1] #+ random.gauss(0, 2) 273 s.orientation += avgAngularSpeed * timeStepLength #+ random.gauss(0, 2) 274 # normalize (according to the range of atan2() ) 275 while( s.orientation > 180 ): 276 s.orientation -= 360 277 while( s.orientation <= -180 ): 278 s.orientation +=360 279 s.speed += forwardAcceleration * timeStepLength 280 s.angularSpeed += angularAcceleration * timeStepLength 281 282 # print 'new state', s.x, s.y, s.orientation, s.speed, s.angularSpeed 283 # print 284 285 return
286 287 # # Original code that is simple but not realistic movement 288 # ship = self.state.ships[shipIndex] 289 # s = self.state.shipExternalStates[shipIndex] 290 # speed = ship.engineSpeed# TODO: Incorrect, change! 291 # s.orientation += ship.steering # TODO: Incorrect, change! 292 # xTranslation = speed * cos(radians(s.orientation)) 293 # yTranslation = speed * sin(radians(s.orientation)) 294 # # TODO: does it affect the original state? 295 # s.x += xTranslation 296 # s.y += yTranslation 297
298 - def computeChangeInSeaConditions(self, timePassed):
299 "Compute sea state change in time" 300 # TODO: implement 301 pass
302 303 304 305 ##################################### 306 # COMMUNICATION MODULE 307 #####################################
308 - def processAgentsMessages(self):
309 """ 310 The communication module of the simulator. 311 Each round, all agents' messages are being processed. 312 """ 313 for agentIndex, agent in enumerate(self.shipAgents): 314 compositeMsg = agent.getOutgoingMessage() 315 self.processCompositeMessage(compositeMsg, agentIndex)
316
317 - def processCompositeMessage(self, compositeMsg, agentIndex):
318 """ 319 Processes a Composite messages by breaking it into primitive messages 320 and using the appropriate callbacks based on the message type. 321 322 @type compositeMsg: compositeMsg 323 @param compositeMsg: Contains 0 or more primitive messages from one agent. 324 325 @type agentIndex: int 326 @param agentIndex: sort of an agent-id - an agent's index in the array of all agents 327 """ 328 for msg in compositeMsg.primitiveMsgs: 329 # go to callback name, and convert to function using getattr 330 getattr(self, self.msgCallbacks[msg.type])(msg, agentIndex)
331
332 - def processMsgVisit(self, msg, agentIndex):
333 """Processes a msg that notifies about an agent visit at a point. 334 335 @type msg: messages.PrimitiveMessage 336 @param msg: a message with a specifically assumed format (see below) 337 338 @type agentIndex: int 339 @param agentIndex: sort of an agent-id - an agent's index in the array of all agents 340 """ 341 # format is assumed to be "(x,y)" so we just evaluate it 342 point = msg.content 343 344 # Note: here we don't use the "to" field of the msg, 345 # as it's assumed to be sent to the environment 346 347 # just a regular dictionary assignment visitTimes[point].append(self.time) 348 # that automatically takes care of initialization. 349 self.visitTimes.setdefault(point, []).append(self.time)
350 351 #print 'processMsgVisit', point, self.time 352
353 - def processMsgEdgeLength(self, msg, agentIndex):
354 """ 355 Processes a msg that notifies the length of 356 an edge that an agent just completed. 357 358 @type msg: messages.PrimitiveMessage 359 @param msg: a message with a specifically assumed format (see below) 360 361 @type agentIndex: int 362 @param agentIndex: sort of an agent-id - an agent's index in the array of all agents 363 """ 364 # format is assumed to be "(((x1,y1),(x2,y2)), length)" so we just evaluate it 365 edge, length = msg.content 366 367 # Note: here we don't use the "to" field of the msg, 368 # as it's assumed to be sent to the environment 369 370 # just a regular dictionary assignment visitTimes[point].append(self.time) 371 # that automatically takes care of initialization. 372 self.edgeLengths.setdefault(edge, []).append(length)
373 374 #print 'processMsgEdgeLength', edge, length 375