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

Source Code for Module simulationEnvironment

  1  ################################################################################## 
  2  # Copyright (c) 2010, 2011, 2012, 2013, 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  from messages import PrimitiveMessage 
 44   
45 -class SimulationEnvironment:
46 """ 47 48 Based on AIMA's environment: 49 Abstract class representing an Environment. 50 'Real' Environment classes inherit from this. 51 Your Environment will typically need to implement: 52 percept: Define the percept that an agent sees. 53 executeAction: Define the effects of executing an action. 54 55 """ 56
57 - def __init__( self, initialState, shipAgents ):
58 """ 59 Initialize the simulation environment. 60 61 @type initialState: State 62 @param initialState: The initial state of the sea and ships 63 64 @type shipAgents: array of Agents 65 @param shipAgents: The initial state of the sea and ships 66 """ 67 self.state = initialState 68 self.shipAgents = shipAgents 69 # initializing the callbacks for processing messages 70 self.msgCallbacks = {} 71 self.msgCallbacks[PrimitiveMessage.TYPE_VISIT] = self.processMsgVisit.__name__ 72 self.msgCallbacks[PrimitiveMessage.TYPE_EDGE_LENGTH] = self.processMsgEdgeLength.__name__ 73 # . 74 # . 75 # . 76 # continue adding callbacks 77 78 # different data fields that gather some data about the simulation 79 self.time = 0 # simulation time: 1, 2, ... (logically, 0 is the initial state so couting from 1) 80 self.visitTimes = {} # maps a point to a list of times it has been visited 81 self.edgeLengths = {} # maps an edge to a list of measured edge travel-times
82 83 84
85 - def percept(self, agentIndex):
86 """ 87 Return the percept that the agent sees at this point. 88 We view the world such that the complete state is sent to 89 a ship, and a ship can only extract from it what it can percept. 90 """ 91 return self.state.ships[agentIndex].getPercepts(self.state)
92
93 - def executeAction(self, agentIndex, action):
94 """Change the world to reflect this action. 95 Here, we only change the ship's internal state. 96 The external state will be changed in exogenousChange() 97 98 @type agentIndex: int 99 @param agentIndex: sort of an agent-id - an agent's index in the array of all agents 100 101 @type action: CompositeAction 102 @param action: An action chosen by the agent, to be executed on the ship 103 """ 104 self.state.ships[agentIndex].executeAction(action)
105
106 - def exogenousChange(self):
107 """ 108 Compute a change in the world state, that is external to the agent. 109 This function is called in every simulation step, after computing the 110 effects of the agent's actions. 111 112 Here the change that is computed is: 113 - A change in the ships' external states, which are outside the control 114 of the agent. (Ships' internal states are changed by agent actions.) 115 - A change in the sea conditions 116 """ 117 # 2 steps: ship state change, and then 118 # enviroment change as a result of time 119 for i in range(len(self.state.ships)): 120 self.updateShipExternalState(i) 121 122 # TODO: put other value for time(?) 123 self.computeChangeInSeaConditions(timePassed=1)
124
125 - def step(self):
126 """ 127 Run the SimulationEnvironment for one time step (One second). 128 129 Clarification, originally from the AIMA book's code: 130 If the actions and exogenous changes are independent, 131 this method will do. 132 If there are interactions between them, you'll need to 133 override this method. 134 """ 135 self.time += 1 136 137 if not self.isDone(): 138 actions = [agent.getAction(self.percept(agentIndex)) for agentIndex, agent in enumerate(self.shipAgents)] 139 for (agentIndex, action) in enumerate(actions): 140 self.executeAction(agentIndex, action) 141 self.exogenousChange() 142 143 # TODO: currently messages are not part of an "action" - 144 # they are a kind of "metadata" that is being communicated. 145 # If needed, this could be changed in the future. 146 self.processAgentsMessages() 147 148 # ORIGINAL CODE 149 ''' 150 if not self.isDone(): 151 actions = [agent.getAction(self.percept(agent)) for agent in self.shipAgents] 152 for (agentIndex, action) in enumerate(actions): 153 self.executeAction(agentIndex, action) 154 self.exogenousChange() 155 '''
156 157
158 - def isDone(self):
159 "By default, we're done when we can't find a live agent." 160 # for agent in self.agents: 161 # if agent.is_alive(): return False 162 # return True 163 return False
164
165 - def run(self, steps=250000):
166 """Run the SimulationEnvironment for given number of time steps.""" 167 for step in xrange(steps): # xrange is a generator, more efficient 168 if self.isDone(): 169 return 170 self.step() 171 print 'Done!'
172
173 - def updateShipExternalState(self, shipIndex):
174 """ 175 Compute ship position change in time, given the current state. 176 177 Computation of the next state is based on the world state, 178 the ship internal and external state and time passed. 179 180 Currently, we approximate ship movement using the following model: 181 - For forward motion, we model forward force that operates on the ship 182 by the engine, and a drag, which is quadratic in the ship's speed. 183 - For turning, there is an rotational torque that is applied by the rudder, 184 and is proportional to the ship's speed, and to sin(rudder-steering-angle), 185 which is the projection of the rudder on the lateral direction. 186 There is also a rotational drag force, that is quadratic in the ship's 187 angular speed, (need to check about the accuracy of this modelling). 188 - Based on the above forces and the ship's mass, we compute the 189 forward and angular accelerations. 190 - Then based on the average forward and angular speed in a given time step, 191 computed using the above accelerations, we infer the turn radius, 192 and based on that, compute the ship position at the end of this time-step. 193 194 Approximations we make: 195 196 - Although the angular acceleration depends on forward speed, 197 which is changing due to forward acceleration, we still 198 assume constant angular acceleration, based on the avg. speed in this step. 199 - Forward acceleration computation does not take into account the affects 200 of turning which (might?) slow it down. 201 - Forward acceleration depends on the drag, which is changing with speed change, 202 but we approximate the drag based on the initial speed of a time step 203 204 """ 205 206 # TODO: later stage: collisions? 207 208 # Get the needed data 209 ship = self.state.ships[shipIndex] 210 s = self.state.shipExternalStates[shipIndex] 211 shipSpeed = s.speed 212 angularSpeed = s.angularSpeed 213 timeStepLength = 1 # TODO: change that in the future 214 215 216 # Compute the front acceleration and avg speed 217 forwardForce = ship.getForwardForce(shipSpeed) 218 forwardAcceleration = ship.getForwardAccelerationFromForce(forwardForce) 219 # print 'forwardAcceleration', forwardAcceleration 220 # v = v0 + at => avg = (v0 + v) / 2 = (v0 + v0 + at) / 2 = v0 + at/2 221 avgSpeed = shipSpeed + forwardAcceleration * timeStepLength * 0.5 222 # print 'avgSpeed', avgSpeed 223 224 # Compute the angular acceleration and avg speed 225 steeringForce = ship.getSteeringForce(avgSpeed, angularSpeed) 226 angularAcceleration = ship.getAngularAccelerationFromForce(steeringForce) 227 # print 'angularAcceleration', angularAcceleration 228 avgAngularSpeed = angularSpeed + angularAcceleration * timeStepLength * 0.5 229 # print 'avgAngularSpeed', avgAngularSpeed 230 avgAngularSpeedRad = radians(avgAngularSpeed) 231 # print 'avgAngularSpeedRad', avgAngularSpeedRad 232 233 234 # Compute the new position at the end of this time step 235 # TODO: This is an approximation. We compute the avg speed and the avg angular 236 # speed, and using a geometric computation for motion in constant 237 # forward / angular speeds. 238 239 if avgAngularSpeedRad == 0: # going straight 240 relXTranslation = avgSpeed * timeStepLength 241 relYTranslation = 0 242 else: 243 radius = float(avgSpeed) / avgAngularSpeedRad # the 0.0 is for floating point division 244 relXTranslation = radius * sin(avgAngularSpeedRad * timeStepLength) # yes, it's sine... 245 relYTranslation = radius * (1 - cos(avgAngularSpeedRad * timeStepLength)) # 246 # A fix for turning in negative direction 247 if avgAngularSpeedRad < 0: 248 relYTranslation = -relYTranslation 249 # print 'relXTranslation', relXTranslation, 'relYTranslation', relYTranslation 250 251 startAngle = s.orientation 252 # print 'startAngle', startAngle 253 cos_start_angle = cos(radians(startAngle)) 254 sin_start_angle = sin(radians(startAngle)) 255 frontDirectionVector = numpy.array( [cos_start_angle , sin_start_angle] ) 256 # print 'frontDirectionVector', frontDirectionVector 257 sideDirectionVector = numpy.array( [-sin_start_angle, cos_start_angle] ) 258 # print 'sideDirectionVector', sideDirectionVector 259 shipPos = numpy.array( [s.x, s.y] ) 260 # print 'shipPos', shipPos 261 shipNewPos = shipPos + relXTranslation * frontDirectionVector + relYTranslation * sideDirectionVector 262 # print 'shipNewPos', shipNewPos 263 264 # Offset caused by environment conditions 265 sea = self.state.sea 266 shipNewPos += ship.getOffsetByEnvConditions(sea.wind.getSpeedVectorInLocation(s.x, s.y), 267 sea.waterCurrents.getSpeedVectorInLocation(s.x, s.y), 268 startAngle) 269 # TODO: for DEBUG 270 progressVector = shipNewPos - shipPos 271 # print 'progressAngle', degrees(atan2(progressVector[1], progressVector[0])) 272 273 s.x = shipNewPos[0] #+ random.gauss(0, 2) 274 s.y = shipNewPos[1] #+ random.gauss(0, 2) 275 s.orientation += avgAngularSpeed * timeStepLength #+ random.gauss(0, 2) 276 # normalize (according to the range of atan2() ) 277 while( s.orientation > 180 ): 278 s.orientation -= 360 279 while( s.orientation <= -180 ): 280 s.orientation +=360 281 s.speed += forwardAcceleration * timeStepLength 282 s.angularSpeed += angularAcceleration * timeStepLength 283 284 # print 'new state', s.x, s.y, s.orientation, s.speed, s.angularSpeed 285 # print 286 287 return
288 289 # # Original code that is simple but not realistic movement 290 # ship = self.state.ships[shipIndex] 291 # s = self.state.shipExternalStates[shipIndex] 292 # speed = ship.engineSpeed# TODO: Incorrect, change! 293 # s.orientation += ship.steering # TODO: Incorrect, change! 294 # xTranslation = speed * cos(radians(s.orientation)) 295 # yTranslation = speed * sin(radians(s.orientation)) 296 # # TODO: does it affect the original state? 297 # s.x += xTranslation 298 # s.y += yTranslation 299
300 - def computeChangeInSeaConditions(self, timePassed):
301 "Compute sea state change in time" 302 # TODO: implement 303 pass
304 305 306 307 ##################################### 308 # COMMUNICATION MODULE 309 #####################################
310 - def processAgentsMessages(self):
311 """ 312 The communication module of the simulator. 313 Each round, all agents' messages are being processed. 314 """ 315 for agentIndex, agent in enumerate(self.shipAgents): 316 compositeMsg = agent.getOutgoingMessage() 317 self.processCompositeMessage(compositeMsg, agentIndex)
318
319 - def processCompositeMessage(self, compositeMsg, agentIndex):
320 """ 321 Processes a Composite messages by breaking it into primitive messages 322 and using the appropriate callbacks based on the message type. 323 324 @type compositeMsg: compositeMsg 325 @param compositeMsg: Contains 0 or more primitive messages from one agent. 326 327 @type agentIndex: int 328 @param agentIndex: sort of an agent-id - an agent's index in the array of all agents 329 """ 330 for msg in compositeMsg.primitiveMsgs: 331 if msg.to == PrimitiveMessage.TO_SIMULATOR: 332 # go to callback name, and convert to function using getattr 333 getattr(self, self.msgCallbacks[msg.type])(msg, agentIndex) 334 335 elif msg.to == PrimitiveMessage.TO_ALL: 336 for agent in self.shipAgents: 337 agent.receiveMsg(msg) 338 339 else: 340 raise Exception("Unknown message receipient")
341 342
343 - def processMsgVisit(self, msg, agentIndex):
344 """Processes a msg that notifies about an agent visit at a point. 345 346 @type msg: messages.PrimitiveMessage 347 @param msg: a message with a specifically assumed format (see below) 348 349 @type agentIndex: int 350 @param agentIndex: sort of an agent-id - an agent's index in the array of all agents 351 """ 352 # format is assumed to be "(x,y)" so we just evaluate it 353 point = msg.content 354 355 # Note: here we don't use the "to" field of the msg, 356 # as it's assumed to be sent to the environment 357 358 # just a regular dictionary assignment visitTimes[point].append(self.time) 359 # that automatically takes care of initialization. 360 self.visitTimes.setdefault(point, []).append(self.time)
361 362 #print 'processMsgVisit', point, self.time 363
364 - def processMsgEdgeLength(self, msg, agentIndex):
365 """ 366 Processes a msg that notifies the length of 367 an edge that an agent just completed. 368 369 @type msg: messages.PrimitiveMessage 370 @param msg: a message with a specifically assumed format (see below) 371 372 @type agentIndex: int 373 @param agentIndex: sort of an agent-id - an agent's index in the array of all agents 374 """ 375 # format is assumed to be "(((x1,y1),(x2,y2)), length)" so we just evaluate it 376 edge, length = msg.content 377 378 # Note: here we don't use the "to" field of the msg, 379 # as it's assumed to be sent to the environment 380 381 # just a regular dictionary assignment visitTimes[point].append(self.time) 382 # that automatically takes care of initialization. 383 self.edgeLengths.setdefault(edge, []).append(length)
384 385 #print 'processMsgEdgeLength', edge, length 386