Advertisement
creamygoat

Visualising the Segmented-CTE maze-solving in CS373 Unit6-6

May 11th, 2012
484
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 34.55 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. #-------------------------------------------------------------------------------
  4. # Visualising the Segmented-CTE maze-solving in CS373 Unit6-6
  5. #
  6. # unit0606_segmented_cte_svg.py
  7. # http://pastebin.com/Jfyyyhxk
  8. #
  9. # Custom modules:
  10. #   vegesvgplot.py        http://pastebin.com/6Aek3Exm
  11. #
  12. #-------------------------------------------------------------------------------
  13.  
  14.  
  15. '''Udacity CS373 Unit 6-6 robot car demo
  16.  
  17. Description:
  18.  
  19.  This Python script runs a simulation of a robot car performing
  20.  A* search, path smoothing, PID control and naivigation with
  21.  horribly imprecise simulated GPS data. The output is in the form
  22.  of a Scalable Vector Graphic file named “output.svg”
  23.  
  24. Author(s):
  25.  
  26.  Daniel Neville (Blancmange), creamygoat@gmail.com
  27.  Prof. Sebastian Thrun, udacity (original robot simulation)
  28.  
  29. Copyright, licence:
  30.  
  31.  Code by Daniel Neville: Public domain
  32.  Code snarfed from udacity: See http://www.udacity.com/legal/
  33.  
  34. Platform:
  35.  
  36.  Python 2.5
  37.  
  38.  
  39. INDEX
  40.  
  41.  
  42. Fun stuff:
  43.  
  44.  RunUnitCode(Data) – Snarfed and modified robot code
  45.  RenderToSVG(Data)
  46.  DoFunStuff(Data)
  47.  
  48. Main:
  49.  
  50.  Main()
  51.  
  52. '''
  53.  
  54.  
  55. #-------------------------------------------------------------------------------
  56.  
  57.  
  58. import math
  59.  
  60. from math import (
  61.   pi, sqrt, hypot, sin, cos, tan, asin, acos, atan, atan2, radians, degrees,
  62.   floor, ceil, exp
  63. )
  64.  
  65. import random
  66.  
  67. # The SVG Plotting for Vegetables module can be found at
  68. # http://pastebin.com/6Aek3Exm
  69.  
  70. from vegesvgplot import (
  71.  
  72.   # Shape constants
  73.   Pt_Break, Pt_Anchor, Pt_Control,
  74.   PtCmdWithCoordsSet, PtCmdSet,
  75.  
  76.   # Indent tracker class
  77.   tIndentTracker,
  78.  
  79.   # Affine matrix class
  80.   tAffineMtx,
  81.  
  82.   # Affine matrix creation functions
  83.   AffineMtxTS, AffineMtxTRS2D, Affine2DMatrices,
  84.  
  85.   # Utility functions
  86.   ValidatedRange, MergedDictionary, Save,
  87.   ArrayDimensions, NewMDArray, CopyArray, At, SetAt,
  88.  
  89.   # Basic vector functions
  90.   VZeros, VOnes, VStdBasis, VDim, VAug, VMajorAxis,
  91.   VNeg, VSum, VDiff, VSchur, VDot,
  92.   VLengthSquared, VLength, VManhattan,
  93.   VScaled, VNormalised,
  94.   VPerp, VCrossProduct, VCrossProduct4D,
  95.   VScalarTripleProduct, VVectorTripleProduct,
  96.   VProjectionOnto,
  97.   VTransposedMAV,
  98.   VRectToPol, VPolToRect,
  99.   VLerp,
  100.  
  101.   # Shape functions
  102.   ShapeFromVertices, ShapePoints, ShapeSubpathRanges, ShapeCurveRanges,
  103.   ShapeLength, LineToShapeIntersections, TransformedShape, PiecewiseArc,
  104.  
  105.   # Output formatting functions
  106.   MaxDP, GFListStr, GFTupleStr, HTMLEscaped, AttrMarkup, ProgressColourStr,
  107.  
  108.   # SVG functions
  109.   SVGStart, SVGEnd, SVGPathDataSegments, SVGPath, SVGText,
  110.   SVGGroup, SVGGroupEnd, SVGGrid
  111.  
  112. )
  113.  
  114.  
  115. #-------------------------------------------------------------------------------
  116. # Fun stuff
  117. #-------------------------------------------------------------------------------
  118.  
  119.  
  120. def RunUnitCode(Data):
  121.  
  122.   Data['Sparks'] = []
  123.   Data['EstimatedPath'] = []
  124.   Data['ActualPath'] = []
  125.  
  126.   #-----------------------------------------------------------------------------
  127.   # Code snarfed from udacity and modified
  128.   #-----------------------------------------------------------------------------
  129.  
  130.   # don't change the noise paameters
  131.  
  132.   steering_noise    = 0.1
  133.   distance_noise    = 0.03
  134.   measurement_noise = 0.3
  135.  
  136.  
  137.   class plan:
  138.  
  139.       # --------
  140.       # init:
  141.       #    creates an empty plan
  142.       #
  143.  
  144.       def __init__(self, grid, init, goal, cost = 1):
  145.           self.cost = cost
  146.           self.grid = grid
  147.           self.init = init
  148.           self.goal = goal
  149.           self.make_heuristic(grid, goal, self.cost)
  150.           self.path = []
  151.           self.spath = []
  152.  
  153.       # --------
  154.       #
  155.       # make heuristic function for a grid
  156.  
  157.       def make_heuristic(self, grid, goal, cost):
  158.           self.heuristic = [[0 for row in range(len(grid[0]))]
  159.                             for col in range(len(grid))]
  160.           for i in range(len(self.grid)):
  161.               for j in range(len(self.grid[0])):
  162.                   self.heuristic[i][j] = abs(i - self.goal[0]) + \
  163.                       abs(j - self.goal[1])
  164.  
  165.  
  166.  
  167.       # ------------------------------------------------
  168.       #
  169.       # A* for searching a path to the goal
  170.       #
  171.       #
  172.  
  173.       def astar(self):
  174.  
  175.  
  176.           if self.heuristic == []:
  177.               raise ValueError, "Heuristic must be defined to run A*"
  178.  
  179.           # internal motion parameters
  180.           delta = [[-1,  0], # go up
  181.                    [ 0,  -1], # go left
  182.                    [ 1,  0], # go down
  183.                    [ 0,  1]] # do right
  184.  
  185.  
  186.           # open list elements are of the type: [f, g, h, x, y]
  187.  
  188.           closed = [[0 for row in range(len(self.grid[0]))]
  189.                     for col in range(len(self.grid))]
  190.           action = [[0 for row in range(len(self.grid[0]))]
  191.                     for col in range(len(self.grid))]
  192.  
  193.           closed[self.init[0]][self.init[1]] = 1
  194.  
  195.  
  196.           x = self.init[0]
  197.           y = self.init[1]
  198.           h = self.heuristic[x][y]
  199.           g = 0
  200.           f = g + h
  201.  
  202.           open = [[f, g, h, x, y]]
  203.  
  204.           found  = False # flag that is set when search complete
  205.           resign = False # flag set if we can't find expand
  206.           count  = 0
  207.  
  208.  
  209.           while not found and not resign:
  210.  
  211.               # check if we still have elements on the open list
  212.               if len(open) == 0:
  213.                   resign = True
  214.                   print '###### Search terminated without success'
  215.  
  216.               else:
  217.                   # remove node from list
  218.                   open.sort()
  219.                   open.reverse()
  220.                   next = open.pop()
  221.                   x = next[3]
  222.                   y = next[4]
  223.                   g = next[1]
  224.  
  225.               # check if we are done
  226.  
  227.               if x == goal[0] and y == goal[1]:
  228.                   found = True
  229.                   # print '###### A* search successful'
  230.  
  231.               else:
  232.                   # expand winning element and add to new open list
  233.                   for i in range(len(delta)):
  234.                       x2 = x + delta[i][0]
  235.                       y2 = y + delta[i][1]
  236.                       if x2 >= 0 and x2 < len(self.grid) and y2 >= 0 \
  237.                               and y2 < len(self.grid[0]):
  238.                           if closed[x2][y2] == 0 and self.grid[x2][y2] == 0:
  239.                               g2 = g + self.cost
  240.                               h2 = self.heuristic[x2][y2]
  241.                               f2 = g2 + h2
  242.                               open.append([f2, g2, h2, x2, y2])
  243.                               closed[x2][y2] = 1
  244.                               action[x2][y2] = i
  245.  
  246.               count += 1
  247.  
  248.           # extract the path
  249.  
  250.  
  251.  
  252.           invpath = []
  253.           x = self.goal[0]
  254.           y = self.goal[1]
  255.           invpath.append([x, y])
  256.           while x != self.init[0] or y != self.init[1]:
  257.               x2 = x - delta[action[x][y]][0]
  258.               y2 = y - delta[action[x][y]][1]
  259.               x = x2
  260.               y = y2
  261.               invpath.append([x, y])
  262.  
  263.           self.path = []
  264.           for i in range(len(invpath)):
  265.               self.path.append(invpath[len(invpath) - 1 - i])
  266.  
  267.  
  268.  
  269.  
  270.       # ------------------------------------------------
  271.       #
  272.       # this is the smoothing function
  273.       #
  274.  
  275.  
  276.  
  277.  
  278.       def smooth(self, weight_data = 0.1, weight_smooth = 0.1,
  279.                  tolerance = 0.000001):
  280.  
  281.           if self.path == []:
  282.               raise ValueError, "Run A* first before smoothing path"
  283.  
  284.           self.spath = [[0 for row in range(len(self.path[0]))] \
  285.                              for col in range(len(self.path))]
  286.           for i in range(len(self.path)):
  287.               for j in range(len(self.path[0])):
  288.                   self.spath[i][j] = self.path[i][j]
  289.  
  290.           change = tolerance
  291.           while change >= tolerance:
  292.               change = 0.0
  293.               for i in range(1, len(self.path)-1):
  294.                   for j in range(len(self.path[0])):
  295.                       aux = self.spath[i][j]
  296.  
  297.                       self.spath[i][j] += weight_data * \
  298.                           (self.path[i][j] - self.spath[i][j])
  299.  
  300.                       self.spath[i][j] += weight_smooth * \
  301.                           (self.spath[i-1][j] + self.spath[i+1][j]
  302.                            - (2.0 * self.spath[i][j]))
  303.                       if i >= 2:
  304.                           self.spath[i][j] += 0.5 * weight_smooth * \
  305.                               (2.0 * self.spath[i-1][j] - self.spath[i-2][j]
  306.                                - self.spath[i][j])
  307.                       if i <= len(self.path) - 3:
  308.                           self.spath[i][j] += 0.5 * weight_smooth * \
  309.                               (2.0 * self.spath[i+1][j] - self.spath[i+2][j]
  310.                                - self.spath[i][j])
  311.  
  312.               change += abs(aux - self.spath[i][j])
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319.  
  320.   # ------------------------------------------------
  321.   #
  322.   # this is the robot class
  323.   #
  324.  
  325.   class robot:
  326.  
  327.       # --------
  328.       # init:
  329.       # creates robot and initializes location/orientation to 0, 0, 0
  330.       #
  331.  
  332.       def __init__(self, length = 0.5):
  333.           self.x = 0.0
  334.           self.y = 0.0
  335.           self.orientation = 0.0
  336.           self.length = length
  337.           self.steering_noise    = 0.0
  338.           self.distance_noise    = 0.0
  339.           self.measurement_noise = 0.0
  340.           self.num_collisions    = 0
  341.           self.num_steps         = 0
  342.  
  343.       # --------
  344.       # set:
  345.       # sets a robot coordinate
  346.       #
  347.  
  348.       def set(self, new_x, new_y, new_orientation):
  349.  
  350.           self.x = float(new_x)
  351.           self.y = float(new_y)
  352.           self.orientation = float(new_orientation) % (2.0 * pi)
  353.  
  354.  
  355.       # --------
  356.       # set_noise:
  357.       # sets the noise parameters
  358.       #
  359.  
  360.       def set_noise(self, new_s_noise, new_d_noise, new_m_noise):
  361.           # makes it possible to change the noise parameters
  362.           # this is often useful in particle filters
  363.           self.steering_noise     = float(new_s_noise)
  364.           self.distance_noise    = float(new_d_noise)
  365.           self.measurement_noise = float(new_m_noise)
  366.  
  367.       # --------
  368.       # check:
  369.       #    checks of the robot pose collides with an obstacle, or
  370.       # is too far outside the plane
  371.  
  372.       def check_collision(self, grid):
  373.           for i in range(len(grid)):
  374.               for j in range(len(grid[0])):
  375.                   if grid[i][j] == 1:
  376.                       dist = sqrt((self.x - float(i)) ** 2 +
  377.                                   (self.y - float(j)) ** 2)
  378.                       if dist < 0.5:
  379.                           self.num_collisions += 1
  380.                           return False
  381.           return True
  382.  
  383.       def check_goal(self, goal, threshold = 1.0):
  384.           Data['GoalThreshold'] = threshold
  385.           dist =  sqrt((float(goal[0]) - self.x) ** 2 + (float(goal[1]) - self.y) ** 2)
  386.           return dist < threshold
  387.  
  388.       # --------
  389.       # move:
  390.       #    steering = front wheel steering angle, limited by max_steering_angle
  391.       #    distance = total distance driven, most be non-negative
  392.  
  393.       def move(self, grid, steering, distance,
  394.                tolerance = 0.001, max_steering_angle = pi / 4.0):
  395.  
  396.           if steering > max_steering_angle:
  397.               steering = max_steering_angle
  398.           if steering < -max_steering_angle:
  399.               steering = -max_steering_angle
  400.           if distance < 0.0:
  401.               distance = 0.0
  402.  
  403.  
  404.           # make a new copy
  405.           res = robot()
  406.           res.length            = self.length
  407.           res.steering_noise    = self.steering_noise
  408.           res.distance_noise    = self.distance_noise
  409.           res.measurement_noise = self.measurement_noise
  410.           res.num_collisions    = self.num_collisions
  411.           res.num_steps         = self.num_steps + 1
  412.  
  413.           # apply noise
  414.           steering2 = random.gauss(steering, self.steering_noise)
  415.           distance2 = random.gauss(distance, self.distance_noise)
  416.  
  417.  
  418.           # Execute motion
  419.           turn = tan(steering2) * distance2 / res.length
  420.  
  421.           if abs(turn) < tolerance:
  422.  
  423.               # approximate by straight line motion
  424.  
  425.               res.x = self.x + (distance2 * cos(self.orientation))
  426.               res.y = self.y + (distance2 * sin(self.orientation))
  427.               res.orientation = (self.orientation + turn) % (2.0 * pi)
  428.  
  429.           else:
  430.  
  431.               # approximate bicycle model for motion
  432.  
  433.               radius = distance2 / turn
  434.               cx = self.x - (sin(self.orientation) * radius)
  435.               cy = self.y + (cos(self.orientation) * radius)
  436.               res.orientation = (self.orientation + turn) % (2.0 * pi)
  437.               res.x = cx + (sin(res.orientation) * radius)
  438.               res.y = cy - (cos(res.orientation) * radius)
  439.  
  440.           # check for collision
  441.           # res.check_collision(grid)
  442.  
  443.           return res
  444.  
  445.       # --------
  446.       # sense:
  447.       #
  448.  
  449.       def sense(self):
  450.  
  451.           return [random.gauss(self.x, self.measurement_noise),
  452.                   random.gauss(self.y, self.measurement_noise)]
  453.  
  454.       # --------
  455.       # measurement_prob
  456.       #    computes the probability of a measurement
  457.       #
  458.  
  459.       def measurement_prob(self, measurement):
  460.  
  461.           # compute errors
  462.           error_x = measurement[0] - self.x
  463.           error_y = measurement[1] - self.y
  464.  
  465.           # calculate Gaussian
  466.           error = exp(- (error_x ** 2) / (self.measurement_noise ** 2) / 2.0) \
  467.               / sqrt(2.0 * pi * (self.measurement_noise ** 2))
  468.           error *= exp(- (error_y ** 2) / (self.measurement_noise ** 2) / 2.0) \
  469.               / sqrt(2.0 * pi * (self.measurement_noise ** 2))
  470.  
  471.           return error
  472.  
  473.  
  474.  
  475.       def __repr__(self):
  476.           # return '[x=%.5f y=%.5f orient=%.5f]'  % (self.x, self.y, self.orientation)
  477.           return '[%.5f, %.5f]'  % (self.x, self.y)
  478.  
  479.  
  480.  
  481.  
  482.  
  483.  
  484.   # ------------------------------------------------
  485.   #
  486.   # this is the particle filter class
  487.   #
  488.  
  489.   class particles:
  490.  
  491.       # --------
  492.       # init:
  493.       # creates particle set with given initial position
  494.       #
  495.  
  496.       def __init__(self, x, y, theta,
  497.                    steering_noise, distance_noise, measurement_noise, N = 100):
  498.           self.N = N
  499.           self.steering_noise    = steering_noise
  500.           self.distance_noise    = distance_noise
  501.           self.measurement_noise = measurement_noise
  502.  
  503.           self.data = []
  504.           for i in range(self.N):
  505.               r = robot()
  506.               r.set(x, y, theta)
  507.               r.set_noise(steering_noise, distance_noise, measurement_noise)
  508.               self.data.append(r)
  509.  
  510.  
  511.       # --------
  512.       #
  513.       # extract position from a particle set
  514.       #
  515.  
  516.       def get_position(self):
  517.           x = 0.0
  518.           y = 0.0
  519.           orientation = 0.0
  520.  
  521.           for i in range(self.N):
  522.               x += self.data[i].x
  523.               y += self.data[i].y
  524.               # orientation is tricky because it is cyclic. By normalizing
  525.               # around the first particle we are somewhat more robust to
  526.               # the 0=2pi problem
  527.               orientation += (((self.data[i].orientation
  528.                                 - self.data[0].orientation + pi) % (2.0 * pi))
  529.                               + self.data[0].orientation - pi)
  530.           return [x / self.N, y / self.N, orientation / self.N]
  531.  
  532.       # --------
  533.       #
  534.       # motion of the particles
  535.       #
  536.  
  537.       def move(self, grid, steer, speed):
  538.           newdata = []
  539.  
  540.           for i in range(self.N):
  541.               r = self.data[i].move(grid, steer, speed)
  542.               newdata.append(r)
  543.           self.data = newdata
  544.  
  545.       # --------
  546.       #
  547.       # sensing and resampling
  548.       #
  549.  
  550.       def sense(self, Z):
  551.           w = []
  552.           for i in range(self.N):
  553.               w.append(self.data[i].measurement_prob(Z))
  554.  
  555.           # resampling (careful, this is using shallow copy)
  556.           p3 = []
  557.           index = int(random.random() * self.N)
  558.           beta = 0.0
  559.           mw = max(w)
  560.  
  561.           for i in range(self.N):
  562.               beta += random.random() * 2.0 * mw
  563.               while beta > w[index]:
  564.                   beta -= w[index]
  565.                   index = (index + 1) % self.N
  566.               p3.append(self.data[index])
  567.           self.data = p3
  568.  
  569.  
  570.  
  571.  
  572.  
  573.  
  574.  
  575.   # --------
  576.   #
  577.   # run:  runs control program for the robot
  578.   #
  579.  
  580.  
  581.   def run(grid, goal, spath, params, printflag = False, speed = 0.1, timeout = 1000):
  582.  
  583.       myrobot = robot()
  584.       myrobot.set(0., 0., 0.)
  585.       Data['ActualPath'].append((myrobot.x, myrobot.y))
  586.       myrobot.set_noise(steering_noise, distance_noise, measurement_noise)
  587.       filter = particles(myrobot.x, myrobot.y, myrobot.orientation,
  588.                          steering_noise, distance_noise, measurement_noise)
  589.  
  590.       cte  = 0.0
  591.       err  = 0.0
  592.       N    = 0
  593.  
  594.       index = 0 # index into the path
  595.  
  596.       while not myrobot.check_goal(goal) and N < timeout:
  597.  
  598.           diff_cte = - cte
  599.  
  600.  
  601.           # ----------------------------------------
  602.           # compute the CTE
  603.  
  604.           # start with the present robot estimate
  605.           estimate = filter.get_position()
  606.           Data['EstimatedPath'].append(tuple(estimate))
  607.  
  608.           #-----------------------------------------------------------------------
  609.  
  610.           def VDiff(A, B):
  611.             return tuple(x - y for x, y in zip(A, B))
  612.  
  613.           def VDot(A, B):
  614.             return sum(x * y for x, y in zip(A, B))
  615.  
  616.           def VLengthSquared(A):
  617.             return sum(x * x for x in A)
  618.  
  619.           def VLength(A):
  620.             return sqrt(VLengthSquared(A))
  621.  
  622.           def VScaled(A, Scale):
  623.             return tuple(x * Scale for x in A)
  624.  
  625.           def VNormalised(A):
  626.             return VScaled(A, 1.0 / VLength(A))
  627.  
  628.           def VPerp(A):
  629.             return (-A[1], A[0])
  630.  
  631.           #-----------------------------------------------------------------------
  632.  
  633.           if 0 <= index < len(spath) - 2:
  634.  
  635.             Leg = (spath[index], spath[index + 1])
  636.             # with the origin at the start of the leg,
  637.             # L is the leg vector and K is the car's (estimated) position.
  638.             L = VDiff(Leg[1], Leg[0])
  639.             K = VDiff(estimate, Leg[0])
  640.             LSinister = VPerp(VNormalised(L))
  641.             cte = VDot(K, LSinister)
  642.             u = VDot(K, L) / VLengthSquared(L)
  643.  
  644.             if u > 1.0:
  645.               index += 1
  646.  
  647.           else:
  648.  
  649.             cte = 0.0
  650.  
  651.           # ----------------------------------------
  652.  
  653.  
  654.           diff_cte += cte
  655.  
  656.           steer = - params[0] * cte - params[1] * diff_cte
  657.  
  658.           myrobot = myrobot.move(grid, steer, speed)
  659.           Data['ActualPath'].append((myrobot.x, myrobot.y))
  660.           filter.move(grid, steer, speed)
  661.  
  662.           Z = myrobot.sense()
  663.           filter.sense(Z)
  664.  
  665.           if not myrobot.check_collision(grid):
  666.               Data['Sparks'].append((myrobot.x, myrobot.y))
  667.               print '##### Collision ####'
  668.  
  669.           err += (cte ** 2)
  670.           N += 1
  671.  
  672.           if printflag:
  673.               print myrobot, cte, index, u
  674.  
  675.       return [myrobot.check_goal(goal), myrobot.num_collisions, myrobot.num_steps]
  676.  
  677.  
  678.   # ------------------------------------------------
  679.   #
  680.   # this is our main routine
  681.   #
  682.  
  683.   def main(grid, init, goal, steering_noise, distance_noise, measurement_noise,
  684.        weight_data, weight_smooth, p_gain, d_gain):
  685.  
  686.       path = plan(grid, init, goal)
  687.       path.astar()
  688.       path.smooth(weight_data, weight_smooth)
  689.       Data['PlannedPath'] = path.spath
  690.       return run(grid, goal, path.spath, [p_gain, d_gain])
  691.  
  692.  
  693.  
  694.  
  695.   # ------------------------------------------------
  696.   #
  697.   # input data and parameters
  698.   #
  699.  
  700.  
  701.   # grid format:
  702.   #   0 = navigable space
  703.   #   1 = occupied space
  704.  
  705.   grid = [[0, 1, 0, 0, 0, 0],
  706.           [0, 1, 0, 1, 1, 0],
  707.           [0, 1, 0, 1, 0, 0],
  708.           [0, 0, 0, 1, 0, 1],
  709.           [0, 1, 0, 1, 0, 0]]
  710.  
  711.  
  712.   init = [0, 0]
  713.   goal = [len(grid)-1, len(grid[0])-1]
  714.  
  715.  
  716.   steering_noise    = 0.1
  717.   distance_noise    = 0.03
  718.   measurement_noise = 0.3
  719.  
  720.   weight_data       = 0.1#0.1
  721.   weight_smooth     = 0.1#0.2
  722.   p_gain            = 2.0#2.0
  723.   d_gain            = 7.5#6.0
  724.  
  725.  
  726.   print main(grid, init, goal, steering_noise, distance_noise, measurement_noise,
  727.              weight_data, weight_smooth, p_gain, d_gain)
  728.  
  729.  
  730.  
  731.  
  732.   def twiddle(init_params):
  733.       n_params   = len(init_params)
  734.       dparams    = [1.0 for row in range(n_params)]
  735.       params     = [0.0 for row in range(n_params)]
  736.       K = 10
  737.  
  738.       for i in range(n_params):
  739.           params[i] = init_params[i]
  740.  
  741.  
  742.       best_error = 0.0;
  743.       for k in range(K):
  744.           ret = main(grid, init, goal,
  745.                      steering_noise, distance_noise, measurement_noise,
  746.                      params[0], params[1], params[2], params[3])
  747.           if ret[0]:
  748.               best_error += ret[1] * 100 + ret[2]
  749.           else:
  750.               best_error += 99999
  751.       best_error = float(best_error) / float(k+1)
  752.       print best_error
  753.  
  754.       n = 0
  755.       while sum(dparams) > 0.0000001:
  756.           for i in range(len(params)):
  757.               params[i] += dparams[i]
  758.               err = 0
  759.               for k in range(K):
  760.                   ret = main(grid, init, goal,
  761.                              steering_noise, distance_noise, measurement_noise,
  762.                              params[0], params[1], params[2], params[3], best_error)
  763.                   if ret[0]:
  764.                       err += ret[1] * 100 + ret[2]
  765.                   else:
  766.                       err += 99999
  767.               print float(err) / float(k+1)
  768.               if err < best_error:
  769.                   best_error = float(err) / float(k+1)
  770.                   dparams[i] *= 1.1
  771.               else:
  772.                   params[i] -= 2.0 * dparams[i]
  773.                   err = 0
  774.                   for k in range(K):
  775.                       ret = main(grid, init, goal,
  776.                                  steering_noise, distance_noise, measurement_noise,
  777.                                  params[0], params[1], params[2], params[3], best_error)
  778.                       if ret[0]:
  779.                           err += ret[1] * 100 + ret[2]
  780.                       else:
  781.                           err += 99999
  782.                   print float(err) / float(k+1)
  783.                   if err < best_error:
  784.                       best_error = float(err) / float(k+1)
  785.                       dparams[i] *= 1.1
  786.                   else:
  787.                       params[i] += dparams[i]
  788.                       dparams[i] *= 0.5
  789.           n += 1
  790.           print 'Twiddle #', n, params, ' -> ', best_error
  791.       print ' '
  792.       return params
  793.  
  794.  
  795.   #twiddle([weight_data, weight_smooth, p_gain, d_gain])
  796.  
  797.  
  798.  
  799.   Data['Map'] = grid
  800.   Data['StartPos'] = init
  801.   Data['GoalPos'] = goal
  802.  
  803.  
  804. #-------------------------------------------------------------------------------
  805.  
  806.  
  807. def RenderToSVG(Data):
  808.  
  809.   '''Return Data rendered to an SVG file in a string.
  810.  
  811.  Data is a dictionary with the keys:
  812.  
  813.    Title:
  814.      This title is rendered and is embedded in the SVG header.
  815.    Grid:
  816.      See SVGGrid().
  817.    Map:
  818.      The map is a 2D array of integers for hazards (1) and clear spaces (0).
  819.    StartPos:
  820.      The car starts here. The coordinates are units of map squares and the
  821.      origin is in the centre of Map[0][0].
  822.    GoalPos:
  823.      Hopefully the car ends within GoalThreshold of here.
  824.    GoalThreshold:
  825.      The radius of the goal zone is measured in grid units.
  826.    Sparks:
  827.      Each time a collision occurs, a coordinate pair is added to Sparks.
  828.    PlannedPath:
  829.      Usually a polyline in Shape format, this is the (directed) path the
  830.      car planned to follow as a result of smoothing the result of the A*
  831.      route planner.
  832.    EstimatedPath:
  833.      Also in Shape format, a polylinr of the estimated positions of the
  834.      car given by very noisy sensor measurements.
  835.    Paths:
  836.      For the puspose of visualising progressive path modifications, Paths
  837.      is a list of Shapes to be rendered in red-to-indigo rainbow colours.
  838.    BadPaths:
  839.      Similar to Paths, BadPaths is a list of Shapes to be rendered in some
  840.      muted colour, beneath any of those in Paths.
  841.  
  842.  '''
  843.  
  844.   #-----------------------------------------------------------------------------
  845.  
  846.   # Shorthand
  847.  
  848.   A = Pt_Anchor
  849.   C = Pt_Control
  850.   B = (Pt_Break, None)
  851.  
  852.   #-----------------------------------------------------------------------------
  853.  
  854.   def Field(Name, Default):
  855.     return Data[Name] if Name in Data else Default
  856.  
  857.   #-----------------------------------------------------------------------------
  858.  
  859.   def GreekCross(Centre, ArmLength):
  860.     x, y = Centre
  861.     s = ArmLength
  862.     return [
  863.       (A, (x - s, y)), (A, (x + s, y)), B,
  864.       (A, (x, y - s)), (A, (x, y + s))
  865.     ]
  866.  
  867.   #-----------------------------------------------------------------------------
  868.  
  869.   def SparkSymbolDef(IT, ID, NumPoints=8):
  870.  
  871.     #---------------------------------------------------------------------------
  872.  
  873.     def StarPoint(Ix):
  874.       AngleStep = 2.0 * pi / (2 * NumPoints)
  875.       Theta = AngleStep * float(Ix % (2 * NumPoints))
  876.       Radius = [1.0, 0.3, 0.7, 0.3][Ix % 4]
  877.       return VPolToRect((Radius, Theta))
  878.  
  879.     #---------------------------------------------------------------------------
  880.  
  881.     Result = ''
  882.  
  883.     S = []
  884.     S.append(VLerp(StarPoint(-1), StarPoint(0), 0.5))
  885.     for i in range(2 * NumPoints):
  886.       S.append(StarPoint(i))
  887.     S.append((S[0][0], S[0][1]))
  888.     S = ShapeFromVertices(S, 1)
  889.  
  890.     Result += IT(
  891.       '<symbol id="' + HTMLEscaped(ID) + '" ' + 'viewBox="-1 -1 2 2">'
  892.     )
  893.     IT.StepIn()
  894.     Result += SVGPath(IT, S, {
  895.       'fill': 'rgba(255, 248, 0, 0.25)',
  896.       'stroke': '#f90',
  897.       'stroke-width': '0.02',
  898.       'stroke-linejoin': 'miter',
  899.       'stroke-linecap': 'butt'
  900.     })
  901.     Result += SVGPath(IT, GreekCross((0, 0), 0.1), {
  902.       'fill': 'none',
  903.       'stroke': 'black',
  904.       'stroke-width': '0.008'
  905.     })
  906.     IT.StepOut()
  907.     Result += IT('</symbol>')
  908.  
  909.     return Result
  910.  
  911.   #-----------------------------------------------------------------------------
  912.  
  913.   IT = tIndentTracker('  ')
  914.  
  915.   Result = ''
  916.   SparkSymbol = ''
  917.   Sparks = Field('Sparks', None)
  918.   Title = Field('Title', '(Untitled)')
  919.  
  920.   Result += SVGStart(IT, Title, {
  921.     'width': '28cm',
  922.     'height': '19cm',
  923.     'viewBox': '0 0 28 19'
  924.   })
  925.  
  926.   Result += IT('<defs>')
  927.   IT.StepIn()
  928.   Result += IT(
  929.     '<marker id="ArrowHead"',
  930.     '    viewBox="0 0 10 10" refX="0" refY="5"',
  931.     '    markerUnits="strokeWidth"',
  932.     '    markerWidth="8" markerHeight="6"',
  933.     '    orient="auto">'
  934.     '  <path d="M 0,0  L 10,5  L 0,10  z"/>',
  935.     '</marker>'
  936.   )
  937.   Result += SparkSymbolDef(IT, 'spark') if Sparks is not None else ''
  938.   # More marker, symbol and gradient definitions can go here.
  939.   IT.StepOut()
  940.   Result += IT('</defs>')
  941.  
  942.   # Background
  943.  
  944.   Result += IT(
  945.     '<!-- Background -->',
  946.     '<rect x="0" y="0" width="28" height="19" stroke="none" fill="white"/>'
  947.   )
  948.  
  949.   # Outer group
  950.  
  951.   Result += IT('<!-- Outer group -->')
  952.   Result += SVGGroup(IT, {'stroke': 'black', 'stroke-width': '0.025'})
  953.  
  954.   # Plot with grid
  955.  
  956.   Result += IT('<!-- Grid -->')
  957.   Result += SVGGrid(IT, Data['Grid'])
  958.  
  959.   # Maze
  960.  
  961.   Map = Field('Map', None)
  962.   if Map is not None:
  963.  
  964.     Result += IT('<!-- Hazards -->')
  965.     Result += SVGGroup(IT, {
  966.       'fill': '#a40',
  967.       'stroke': 'black',
  968.       'stroke-width': '0.01',
  969.       'stroke-linecap': 'butt',
  970.     })
  971.  
  972.     for i in range(len(Map)):
  973.       for j in range(len(Map[0])):
  974.         if Map[i][j] != 0:
  975.           Result += IT('<circle cx="%g" cy="%g" r="0.495"/>\n' % (i, j))
  976.  
  977.     Result += SVGGroupEnd(IT)
  978.  
  979.   # Iniial position
  980.  
  981.   StartPos = Field('StartPos', None)
  982.   if StartPos is not None:
  983.  
  984.     S = [
  985.       (A, (-1.0, -1.0)), (A, (-1.0, +1.0)),
  986.       (A, (+1.0, +1.0)), (A, (+1.0, -1.0)), (A, (-1.0, -1.0))
  987.     ]
  988.  
  989.     Result += IT('<!-- Initial position -->')
  990.     Result += SVGPath(IT,
  991.       TransformedShape(AffineMtxTS(StartPos, 0.3), S),
  992.       {'stroke': '#09f', 'stroke-width': '0.1', 'stroke-linecap': 'square'}
  993.     )
  994.  
  995.   # Goal position
  996.  
  997.   GoalPos = Field('GoalPos', None)
  998.   if GoalPos is not None:
  999.  
  1000.     GoalThreshold = Field('GoalThreshold', None)
  1001.     if GoalThreshold is not None:
  1002.       Result += IT('<!-- Goal threshold -->')
  1003.       Result += IT(
  1004.         '<circle cx="%g" cy="%g" r="%g"' %
  1005.           (GoalPos[0], GoalPos[1], GoalThreshold)
  1006.       )
  1007.       IT.StepIn(2)
  1008.       Result += IT(
  1009.         'fill="rgba(0, 255, 0, 0.1)" stroke="#0d0" stroke-width="0.02"/>'
  1010.       )
  1011.       IT.StepOut(2)
  1012.  
  1013.     S = (
  1014.       GreekCross(GoalPos, 0.4) + [B] +
  1015.       PiecewiseArc(GoalPos, 0.25, (0, 2.0 * pi), 8)
  1016.     )
  1017.     Result += IT('<!-- Goal position -->')
  1018.     Result += SVGPath(IT, S, {
  1019.       'stroke': '#0d0', 'stroke-width': '0.1', 'stroke-linecap': 'butt'
  1020.     })
  1021.  
  1022.   # Sparks
  1023.  
  1024.   if Sparks is not None and len(Sparks) > 0:
  1025.     Result += IT('<!-- Points of collision (sparks) -->')
  1026.     Result += SVGGroup(IT, {'transform': 'translate(-0.5, -0.5)'})
  1027.     for SparkPos in Sparks:
  1028.       Result += IT(
  1029.         ('<use x="%g" y="%g"' % (SparkPos[0], SparkPos[1])) +
  1030.         ' width="1" height="1" xlink:href="#spark"/>'
  1031.       )
  1032.     Result += SVGGroupEnd(IT)
  1033.  
  1034.   # Planned path
  1035.  
  1036.   PlannedPath = Field('PlannedPath', None)
  1037.   if PlannedPath is not None:
  1038.     Result += IT('<!-- Planned path -->')
  1039.     Result += SVGPath(IT,
  1040.       ShapeFromVertices(PlannedPath, 1),
  1041.       {'stroke': '#0d0', 'stroke-width': '0.04'}
  1042.     )
  1043.  
  1044.   # Estimated path
  1045.  
  1046.   EstimatedPath = Field('EstimatedPath', None)
  1047.   if EstimatedPath is not None:
  1048.     Result += IT('<!-- Estimated path -->')
  1049.     Result += SVGPath(IT,
  1050.       ShapeFromVertices(EstimatedPath, 1),
  1051.       {
  1052.         'stroke': '#f09',
  1053.         'stroke-width': '0.04',
  1054.         'stroke-dasharray': '0.025 0.05'
  1055.       }
  1056.     )
  1057.  
  1058.   # Actual path
  1059.  
  1060.   ActualPath = Field('ActualPath', None)
  1061.   if ActualPath is not None:
  1062.     Result += IT('<!-- Actual path -->')
  1063.     Result += SVGPath(IT,
  1064.       ShapeFromVertices(ActualPath, 1),
  1065.       {'stroke': '#40f', 'stroke-width': '0.02'}
  1066.     )
  1067.  
  1068.   # Rejected paths
  1069.  
  1070.   BadPaths = Field('BadPaths', None)
  1071.   if BadPaths is not None and len(BadPaths) > 0:
  1072.  
  1073.     Result += IT('<!-- Rejected paths -->')
  1074.     Result += SVGGroup(IT, {
  1075.       'opacity': '0.10', 'stroke': '#ff0099'
  1076.     })
  1077.  
  1078.     NumBadPaths = len(BadPaths)
  1079.  
  1080.     for PathIx, Path in enumerate(BadPaths):
  1081.       Result += SVGPath(IT, Path)
  1082.  
  1083.     Result += SVGGroupEnd(IT)
  1084.  
  1085.   # Paths in rainbow colours
  1086.  
  1087.   Paths = Field('Paths', None)
  1088.   if Paths is not None and len(Paths) > 0:
  1089.  
  1090.     NumPaths = len(Paths)
  1091.  
  1092.     Result += IT('<!-- Paths in rainbow colours -->')
  1093.     for PathIx, Path in enumerate(Paths):
  1094.       if NumPaths >= 2:
  1095.         Progress = float(PathIx) / float(NumPaths - 1)
  1096.       else:
  1097.         Progress = 1.0
  1098.       Opacity = 1.0 if Progress in [0.0, 1.0] else 0.60 + 0.00 * Progress
  1099.       ColourStr = ProgressColourStr(Progress, Opacity)
  1100.       Result += IT('<!-- Path %d, (%.1f%%) -->' % (PathIx, 100.0 * Progress))
  1101.       Result += SVGPath(IT, Path, {"stroke": ColourStr})
  1102.  
  1103.   # End of plot
  1104.  
  1105.   Result += SVGGroupEnd(IT)
  1106.  
  1107.   # Title and legend
  1108.  
  1109.   Result += IT('<!-- Title background -->')
  1110.   Result += IT(
  1111.     '<rect x="0" y="0" width="28" height="1.1" stroke="none" fill="white"/>'
  1112.   )
  1113.  
  1114.   Result += IT('<!-- Title group -->')
  1115.   Result += SVGGroup(IT, {
  1116.     'font-family': 'sans-serif',
  1117.     'font-size': '0.36',
  1118.     'font-weight': 'normal',
  1119.     'fill': 'black',
  1120.     'stroke': 'none'
  1121.   })
  1122.  
  1123.   Result += IT('<!-- Title -->')
  1124.   Result += SVGText(IT, (0.5, 0.82), Title, {
  1125.     'font-size': '0.72',
  1126.     'font-weight': 'bold'
  1127.   })
  1128.  
  1129.   Result += IT('<!-- Legend line labels-->')
  1130.   Result += SVGText(IT, (19.5, 0.82), 'Planned')
  1131.   Result += SVGText(IT, (22.6, 0.82), 'Estimated')
  1132.   Result += SVGText(IT, (26.0, 0.82), 'Actual')
  1133.  
  1134.   Result += IT('<!-- Legend lines -->')
  1135.   Result += SVGGroup(IT, {
  1136.     'fill': 'none',
  1137.     'stroke-width': '0.12',
  1138.     'stroke-linecap': 'round'
  1139.   })
  1140.  
  1141.   Result += SVGPath(IT,
  1142.     [(Pt_Anchor, (18.5, 0.7)), (Pt_Anchor, (19.3, 0.7))],
  1143.     {'stroke': '#0d0'}
  1144.   )
  1145.   Result += SVGPath(IT,
  1146.     [(Pt_Anchor, (21.6, 0.7)), (Pt_Anchor, (22.4, 0.7))],
  1147.     {'stroke': '#f09', 'stroke-dasharray': '0.075 0.15'}
  1148.   )
  1149.  
  1150.   Result += SVGPath(IT,
  1151.     [(Pt_Anchor, (25.0, 0.7)), (Pt_Anchor, (25.8, 0.7))],
  1152.     {'stroke': '#40f'}
  1153.   )
  1154.  
  1155.   Result += SVGGroupEnd(IT)
  1156.  
  1157.   # End of title group
  1158.  
  1159.   Result += SVGGroupEnd(IT)
  1160.  
  1161.   # End of outer group
  1162.  
  1163.   Result += SVGGroupEnd(IT)
  1164.  
  1165.   Result += SVGEnd(IT)
  1166.  
  1167.   return Result
  1168.  
  1169.  
  1170. #-------------------------------------------------------------------------------
  1171.  
  1172.  
  1173. def DoFunStuff(Data):
  1174.  
  1175.   '''Drive a robot car though a maze.
  1176.  
  1177.  The output is written to the Data (a Python dictionary) to keep the
  1178.  rendering details separate.
  1179.  
  1180.  '''
  1181.  
  1182.   #-----------------------------------------------------------------------------
  1183.  
  1184.   RunUnitCode(Data)
  1185.  
  1186.   Map = Data['Map']
  1187.  
  1188.   #MMS = unichr(0x205F)
  1189.   #MMSEquals = MMS + '=' + MMS
  1190.   #AStr = u'α%s%g' % (MMSEquals, Alpha)
  1191.   #BStr = u'β%s%g' % (MMSEquals,  Beta)
  1192.   #MuStr = u'µ%s%g' % (MMSEquals, Tolerance)
  1193.  
  1194.   Data['Title'] = 'Unit6-6: Maze driving'
  1195.  
  1196.   Grid = {
  1197.     'CanvasMinima': (0.5, 1.5),
  1198.     'CanvasMaxima': (27.5, 18.5),
  1199.     'RangeMinima': (0, 0),
  1200.     'RangeMaxima': (len(Map), len(Map[0])),
  1201.     'YIsUp': False,
  1202.     'Transpose': True,
  1203.     'SquareAlignment': 'Centre',
  1204.     'DrawGrid': True,
  1205.     'DrawUnitAxes': False,
  1206.     'GridLineAttributes': {
  1207.       'stroke-width': '0.02', 'stroke': 'rgba(0, 192, 255, 0.5)'
  1208.     },
  1209.     'GeneralAttributes': {
  1210.       'stroke-width': '0.05', 'stroke': 'red'
  1211.     }
  1212.   }
  1213.  
  1214.   Paths = []
  1215.   #PathLog = []
  1216.   #Paths.append(ShapeFromVertices(PathLog, 1))
  1217.  
  1218.   Data['Grid'] = Grid
  1219.   Data['Paths'] = Paths
  1220.   Data['Map'] = Map
  1221.  
  1222.  
  1223. #-------------------------------------------------------------------------------
  1224. # Main
  1225. #-------------------------------------------------------------------------------
  1226.  
  1227.  
  1228. def Main():
  1229.  
  1230.   OutputFileName = 'output.svg'
  1231.  
  1232.   print 'Driving a car through a maze...'
  1233.  
  1234.   Data = {}
  1235.   DoFunStuff(Data)
  1236.  
  1237.   print 'Rendering SVG...'
  1238.   SVG = RenderToSVG(Data)
  1239.   print 'Done.'
  1240.  
  1241.   print 'Saving SVG to "' + OutputFileName + '"...'
  1242.   Save(SVG.encode('utf_8'), OutputFileName)
  1243.   print 'Done.'
  1244.  
  1245.  
  1246. #-------------------------------------------------------------------------------
  1247. # Command line trigger
  1248. #-------------------------------------------------------------------------------
  1249.  
  1250.  
  1251. if __name__ == '__main__':
  1252.   Main()
  1253.  
  1254.  
  1255. #-------------------------------------------------------------------------------
  1256. # End
  1257. #-------------------------------------------------------------------------------
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement