Advertisement
creamygoat

Visualising the Greedy Goat for udacity CS373 unit5-16

May 7th, 2012
604
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 16.29 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. #-------------------------------------------------------------------------------
  4. # Visualising the Greedy Goat for udacity CS373 unit5-16
  5. #
  6. # unit0516_param_optim_svg.py
  7. # http://pastebin.com/3T7Hu6NP
  8. #
  9. # Custom modules:
  10. #   vegesvgplot.py        http://pastebin.com/6Aek3Exm
  11. #
  12. #-------------------------------------------------------------------------------
  13.  
  14.  
  15. '''Visualisation of PID optimisation in Unit 5-16
  16.  
  17. Description:
  18.  This Python program runs the a PID parameter optimiser for the robot
  19.  car presented in unit 5-16 (the car with badly misaligned steering).
  20.  The output is written to a Scalable Vector Graphic file named
  21.  “output.svg”.
  22.  
  23. Author(s):
  24.  Daniel Neville
  25.  Prof. Sebastian Thrun, udacity (original robot simulation)
  26.  
  27. Copyright, Licence:
  28.  Code by Daniel Neville: Public domain
  29.  Code snarfed from udacity: See http://www.udacity.com/legal/
  30.  
  31. Platform:
  32.  Python 2.5
  33.  
  34.  
  35. INDEX
  36.  
  37.  
  38. Imports
  39.  
  40. Fun stuff:
  41.  
  42.  Snarfed and modified robot code
  43.  RenderToSVG(Data)
  44.  TwiddleAndPlot(InitialCTE, Tolerance, YScale, ErrFnIx, Data)
  45.  
  46. Main:
  47.  
  48.  Main()
  49.  
  50. '''
  51.  
  52.  
  53. #-------------------------------------------------------------------------------
  54.  
  55.  
  56. import math
  57.  
  58. from math import (
  59.   pi, sqrt, hypot, sin, cos, tan, asin, acos, atan, atan2, radians, degrees,
  60.   floor, ceil
  61. )
  62.  
  63. import random
  64.  
  65. # The SVG Plotting for Vegetables module can be found at
  66. # http://pastebin.com/6Aek3Exm
  67.  
  68. from vegesvgplot import (
  69.  
  70.   # Shape constants
  71.   Pt_Break, Pt_Anchor, Pt_Control,
  72.   PtCmdWithCoordsSet, PtCmdSet,
  73.  
  74.   # Indent tracker class
  75.   tIndentTracker,
  76.  
  77.   # Affine matrix class
  78.   tAffineMtx,
  79.  
  80.   # Affine matrix creation functions
  81.   AffineMtxTS, AffineMtxTRS2D, Affine2DMatrices,
  82.  
  83.   # Utility functions
  84.   ValidatedRange, MergedDictionary, Save,
  85.   ArrayDimensions, NewMDArray, CopyArray, At, SetAt,
  86.  
  87.   # Basic vector functions
  88.   VZeros, VOnes, VStdBasis, VDim, VAug, VMajorAxis,
  89.   VNeg, VSum, VDiff, VSchur, VDot,
  90.   VLengthSquared, VLength, VManhattan,
  91.   VScaled, VNormalised,
  92.   VPerp, VCrossProduct, VCrossProduct4D,
  93.   VScalarTripleProduct, VVectorTripleProduct,
  94.   VProjectionOnto,
  95.   VTransposedMAV,
  96.   VRectToPol, VPolToRect,
  97.   VLerp,
  98.  
  99.   # Shape functions
  100.   ShapeFromVertices, ShapePoints, ShapeSubpathRanges, ShapeCurveRanges,
  101.   ShapeLength, LineToShapeIntersections, TransformedShape, PiecewiseArc,
  102.  
  103.   # Output formatting functions
  104.   MaxDP, GFListStr, GFTupleStr, HTMLEscaped, AttrMarkup, ProgressColourStr,
  105.  
  106.   # SVG functions
  107.   SVGStart, SVGEnd, SVGPathDataSegments, SVGPath, SVGText,
  108.   SVGGroup, SVGGroupEnd, SVGGrid
  109.  
  110. )
  111.  
  112.  
  113. #-------------------------------------------------------------------------------
  114. # Fun stuff
  115. #-------------------------------------------------------------------------------
  116.  
  117.  
  118. # BEGIN CODE SNARFED FROM UDACITY
  119.  
  120. # NOTE: The function run() has been modified to return a list of
  121. # position vectors in addition to the best error score.
  122.  
  123.  
  124. # ------------------------------------------------
  125. #
  126. # this is the robot class
  127. #
  128.  
  129. class robot:
  130.  
  131.     # --------
  132.     # init:
  133.     #    creates robot and initializes location/orientation to 0, 0, 0
  134.     #
  135.  
  136.     def __init__(self, length = 20.0):
  137.         self.x = 0.0
  138.         self.y = 0.0
  139.         self.orientation = 0.0
  140.         self.length = length
  141.         self.steering_noise = 0.0
  142.         self.distance_noise = 0.0
  143.         self.steering_drift = 0.0
  144.  
  145.     # --------
  146.     # set:
  147.     #   sets a robot coordinate
  148.     #
  149.  
  150.     def set(self, new_x, new_y, new_orientation):
  151.  
  152.         self.x = float(new_x)
  153.         self.y = float(new_y)
  154.         self.orientation = float(new_orientation) % (2.0 * pi)
  155.  
  156.  
  157.     # --------
  158.     # set_noise:
  159.     #   sets the noise parameters
  160.     #
  161.  
  162.     def set_noise(self, new_s_noise, new_d_noise):
  163.         # makes it possible to change the noise parameters
  164.         # this is often useful in particle filters
  165.         self.steering_noise = float(new_s_noise)
  166.         self.distance_noise = float(new_d_noise)
  167.  
  168.     # --------
  169.     # set_steering_drift:
  170.     #   sets the systematical steering drift parameter
  171.     #
  172.  
  173.     def set_steering_drift(self, drift):
  174.         self.steering_drift = drift
  175.  
  176.     # --------
  177.     # move:
  178.     #    steering = front wheel steering angle, limited by max_steering_angle
  179.     #    distance = total distance driven, most be non-negative
  180.  
  181.     def move(self, steering, distance,
  182.              tolerance = 0.001, max_steering_angle = pi / 4.0):
  183.  
  184.         if steering > max_steering_angle:
  185.             steering = max_steering_angle
  186.         if steering < -max_steering_angle:
  187.             steering = -max_steering_angle
  188.         if distance < 0.0:
  189.             distance = 0.0
  190.  
  191.  
  192.         # make a new copy
  193.         res = robot()
  194.         res.length         = self.length
  195.         res.steering_noise = self.steering_noise
  196.         res.distance_noise = self.distance_noise
  197.         res.steering_drift = self.steering_drift
  198.  
  199.         # apply noise
  200.         steering2 = random.gauss(steering, self.steering_noise)
  201.         distance2 = random.gauss(distance, self.distance_noise)
  202.  
  203.         # apply steering drift
  204.         steering2 += self.steering_drift
  205.  
  206.         # Execute motion
  207.         turn = tan(steering2) * distance2 / res.length
  208.  
  209.         if abs(turn) < tolerance:
  210.  
  211.             # approximate by straight line motion
  212.  
  213.             res.x = self.x + (distance2 * cos(self.orientation))
  214.             res.y = self.y + (distance2 * sin(self.orientation))
  215.             res.orientation = (self.orientation + turn) % (2.0 * pi)
  216.  
  217.         else:
  218.  
  219.             # approximate bicycle model for motion
  220.  
  221.             radius = distance2 / turn
  222.             cx = self.x - (sin(self.orientation) * radius)
  223.             cy = self.y + (cos(self.orientation) * radius)
  224.             res.orientation = (self.orientation + turn) % (2.0 * pi)
  225.             res.x = cx + (sin(res.orientation) * radius)
  226.             res.y = cy - (cos(res.orientation) * radius)
  227.  
  228.         return res
  229.  
  230.  
  231.  
  232.  
  233.     def __repr__(self):
  234.         return '[x=%.5f y=%.5f orient=%.5f]'  % (self.x, self.y, self.orientation)
  235.  
  236.  
  237. # ------------------------------------------------------------------------
  238. #
  239. # run - does a single control run.
  240.  
  241.  
  242. def run(params, InitialCTE=1.0, ErrFnIx=0, printflag = False):
  243.  
  244.     myrobot = robot()
  245.     myrobot.set(0.0, InitialCTE, 0.0)
  246.     speed = 1.0
  247.     err = 0.0
  248.     N = 100
  249.     # myrobot.set_noise(0.1, 0.0)
  250.     myrobot.set_steering_drift(10.0 / 180.0 * pi) # 10 degree steering error
  251.  
  252.     Path = [(myrobot.x, myrobot.y)] #<<<<
  253.  
  254.     LastCTE = myrobot.y
  255.     LastDeltaCTE = 0.0
  256.     iCTE = 0.0
  257.  
  258.     for i in range(N * 2):
  259.  
  260.         CTE = myrobot.y
  261.         dCTE = CTE - LastCTE
  262.         ddCTE = dCTE - LastDeltaCTE
  263.         iCTE += CTE
  264.  
  265.         steer = - params[0] * CTE  \
  266.             - params[1] * dCTE \
  267.             - iCTE * params[2]
  268.         myrobot = myrobot.move(steer, speed)
  269.  
  270.         if ErrFnIx == 0:
  271.           if i >= N:
  272.               err += (CTE ** 2)
  273.         elif ErrFnIx == 1:
  274.           #err += CTE ** 2 + 1.0 * (dCTE**2 / (0.01 + CTE**2))
  275.           err += (CTE + 4.0 * dCTE) ** 2 + (20.0 * ddCTE ** 2)
  276.         else:
  277.           raise Error('Unhandled error function index %s.' % str(ErrFnIx))
  278.  
  279.         LastCTE = CTE
  280.         LastDeltaCTE = dCTE
  281.  
  282.         if printflag:
  283.             print myrobot, steer
  284.         Path.append((myrobot.x, myrobot.y)) #<<<<
  285.  
  286.     return err / float(N), Path
  287.  
  288.  
  289. # END CODE SNARFED FROM UDACITY
  290.  
  291.  
  292. #-------------------------------------------------------------------------------
  293.  
  294.  
  295. def RenderToSVG(Data):
  296.  
  297.   '''Return data rendered to an SVG file in a string.
  298.  
  299.  Grid is a dictionary with the keys:
  300.  
  301.    Title: This title is rendered and is embedded in the SVG header.
  302.    Grid: See SVGGrid().
  303.    Paths: A list of Shapes to be rendered in red-to-indigo rainbow colours.
  304.    BadPaths: A list of Shapes to be rendered in a faded colour.
  305.  
  306.  '''
  307.  
  308.   #-----------------------------------------------------------------------------
  309.  
  310.   def Field(Name, Default):
  311.     return Data[Name] if Name in Data else Default
  312.  
  313.   #-----------------------------------------------------------------------------
  314.  
  315.   IT = tIndentTracker('  ')
  316.   Result = ''
  317.  
  318.   Title = Field('Title', '(Untitled)')
  319.  
  320.   Result += SVGStart(IT, Title, {
  321.     'width': '28cm',
  322.     'height': '19cm',
  323.     'viewBox': '0 0 28 19',
  324.   })
  325.  
  326.   Result += IT('<defs>')
  327.   IT.StepIn()
  328.   Result += IT(
  329.     '<marker id="ArrowHead"',
  330.     '    viewBox="0 0 10 10" refX="0" refY="5"',
  331.     '    markerUnits="strokeWidth"',
  332.     '    markerWidth="16" markerHeight="12"',
  333.     '    orient="auto">'
  334.     '  <path d="M 0,0  L 10,5  L 0,10  z"/>',
  335.     '</marker>'
  336.   )
  337.   # More marker, symbol and gradient definitions can go here.
  338.   IT.StepOut()
  339.   Result += IT('</defs>')
  340.  
  341.   # Background
  342.  
  343.   Result += IT(
  344.     '<!-- Background -->',
  345.     '<rect x="0" y="0" width="28" height="19" stroke="none" fill="white"/>'
  346.   )
  347.  
  348.   # Outer group
  349.  
  350.   Result += IT('<!-- Outer group -->')
  351.   Result += SVGGroup(IT, {'stroke': 'black', 'stroke-width': '0.025'})
  352.  
  353.   # Plots of both rejected and tentatively accepted paths
  354.  
  355.   Result += IT('<!-- Grid -->')
  356.   Result += SVGGrid(IT, Data['Grid'])
  357.  
  358.   # Rejected paths
  359.  
  360.   BadPaths = Field('BadPaths', None)
  361.   if BadPaths is not None:
  362.  
  363.     Result += IT('<!-- Rejected paths -->')
  364.     Result += SVGGroup(IT, {
  365.       'opacity': '0.10', 'stroke': '#ff0099'
  366.     })
  367.  
  368.     NumBadPaths = len(BadPaths)
  369.  
  370.     for PathIx, Path in enumerate(BadPaths):
  371.       Result += SVGPath(IT, Path)
  372.  
  373.     Result += SVGGroupEnd(IT)
  374.  
  375.   # Axes
  376.  
  377.   Result += IT('<!-- Axes -->')
  378.   RangeMin = Data['Grid']['RangeMinima']
  379.   RangeMax = Data['Grid']['RangeMaxima']
  380.   Result += SVGGroup(IT, {
  381.     'stroke': 'black',
  382.     'stroke-width': '0.05',
  383.     'stroke-linecap': 'square',
  384.     'marker-end': 'url(#ArrowHead)'
  385.   })
  386.   Result += SVGPath(IT,
  387.     [(Pt_Anchor, (RangeMin[0], 0.0)), (Pt_Anchor, (RangeMax[0] + 0.1, 0.0))]
  388.   )
  389.   Result += SVGPath(IT,
  390.     [(Pt_Anchor, (0.0, RangeMin[1])), (Pt_Anchor, (0.0, RangeMax[1] + 0.1))]
  391.   )
  392.   Result += SVGGroupEnd(IT)
  393.  
  394.   # Paths in rainbow colours
  395.  
  396.   Paths = Field('Paths', None)
  397.   if Paths is not None:
  398.  
  399.     NumPaths = len(Paths)
  400.  
  401.     Result += IT('<!-- Paths in rainbow colours -->')
  402.     for PathIx, Path in enumerate(Paths):
  403.       if NumPaths >= 2:
  404.         Progress = float(PathIx) / float(NumPaths - 1)
  405.       else:
  406.         Progress = 1.0
  407.       Opacity = 1.0 if Progress in [0.0, 1.0] else 0.60 + 0.00 * Progress
  408.       ColourStr = ProgressColourStr(Progress, Opacity)
  409.       Result += IT('<!-- Path %d, (%.1f%%) -->' % (PathIx, 100.0 * Progress))
  410.       Result += SVGPath(IT, Path, {"stroke": ColourStr})
  411.  
  412.   # End of plot
  413.  
  414.   Result += SVGGroupEnd(IT)
  415.  
  416.   # Title and legend
  417.  
  418.   Result += IT('<!-- Title background -->')
  419.   Result += IT(
  420.     '<rect x="0" y="0" width="28" height="1.1" stroke="none" fill="white"/>'
  421.   )
  422.  
  423.   Result += IT('<!-- Title group -->')
  424.   Result += SVGGroup(IT, {
  425.     'font-family': 'sans-serif',
  426.     'font-size': '0.36',
  427.     'font-weight': 'normal',
  428.     'fill': 'black',
  429.     'stroke': 'none'
  430.   })
  431.  
  432.   Result += IT('<!-- Title -->')
  433.   Result += SVGText(IT, (0.5, 0.82), Title, {
  434.     'font-size': '0.72',
  435.     'font-weight': 'bold'
  436.   })
  437.  
  438.   Result += IT('<!-- Legend line labels-->')
  439.   Result += SVGText(IT, (23.5, 0.82), 'Initial')
  440.   Result += SVGText(IT, (26.0, 0.82), 'Final')
  441.  
  442.   Result += IT('<!-- Legend lines -->')
  443.   Result += SVGGroup(IT, {
  444.     'fill': 'none',
  445.     'stroke-width': '0.1',
  446.     'stroke-linecap': 'round'
  447.   })
  448.  
  449.   Result += SVGPath(IT,
  450.     [(Pt_Anchor, (22.5, 0.7)), (Pt_Anchor, (23.3, 0.7))],
  451.     {'stroke': ProgressColourStr(0.0)}
  452.   )
  453.  
  454.   Result += SVGPath(IT,
  455.     [(Pt_Anchor, (25.0, 0.7)), (Pt_Anchor, (25.8, 0.7))],
  456.     {'stroke': ProgressColourStr(1.0)}
  457.   )
  458.  
  459.   Result += SVGGroupEnd(IT)
  460.  
  461.   # End of title group
  462.  
  463.   Result += SVGGroupEnd(IT)
  464.  
  465.   # End of outer group
  466.  
  467.   Result += SVGGroupEnd(IT)
  468.  
  469.   Result += SVGEnd(IT)
  470.  
  471.   return Result
  472.  
  473.  
  474. #-------------------------------------------------------------------------------
  475.  
  476.  
  477. def TwiddleAndPlot(InitialCTE, Tolerance, YScale, ErrFnIx, Data):
  478.  
  479.   '''Find the best PID values for the robot car, graphing the results to Data.
  480.  
  481.  The optimal PID values (Proportional, Differential and Integral) are
  482.  returned as a tuple.
  483.  
  484.  InitialCTE is the initial cross-track error of the car in metres.
  485.  Tolerance is the sum of the adjustments required for the greedy goat
  486.    to consider the PID parameters optimal.
  487.  YScale is the vertical magnification used in the output plot.
  488.  ErrFnIx, the error function index selects the fitness function to use.
  489.  Data is a dictionary to which the output is written.
  490.  
  491.  '''
  492.  
  493.   #-----------------------------------------------------------------------------
  494.  
  495.   # Adaptor function
  496.  
  497.   def Evaluate(P, OtherParams):
  498.     return run(*((P,) + OtherParams))
  499.  
  500.   #-----------------------------------------------------------------------------
  501.  
  502.   Title = 'Unit5-16: Parameter Optimisation'
  503.  
  504.   if YScale != 1.0:
  505.     Title += u' (y × %g)' % (YScale)
  506.  
  507.   Data['Title'] = Title
  508.  
  509.   Grid = {
  510.     'CanvasMinima': (0.5, 1.5),
  511.     'CanvasMaxima': (27.5, 18.5),
  512.     'RangeMinima': (0, -10),
  513.     'RangeMaxima': (100, 10),
  514.     'YIsUp': True,
  515.     'Transpose': False,
  516.     'SquareAlignment': 'Corner',
  517.     'DrawGrid': True,
  518.     'DrawUnitAxes': False,
  519.     'GridLineAttributes': {
  520.       'stroke-width': '0.075', 'stroke': 'rgba(0, 192, 255, 0.5)'
  521.     },
  522.     'GeneralAttributes': {
  523.       'stroke-width': '0.15', 'stroke': 'red'
  524.     }
  525.   }
  526.  
  527.   AM = AffineMtxTS((0.0, 0.0), (1.0, YScale))
  528.  
  529.   # Additional simulation paramters
  530.   OtherParams = (InitialCTE, ErrFnIx)
  531.  
  532.   P = [0.0, 0.0, 0.0]
  533.   DeltaP = [1.0, 1.0, 1.0]
  534.  
  535.   Paths = []
  536.   BadPaths = []
  537.  
  538.   BestErr, Path = Evaluate(P, OtherParams)
  539.   S = ShapeFromVertices(Path, 1)
  540.   if YScale != 1.0:
  541.     S = TransformedShape(AM, S)
  542.   Paths.append(S)
  543.  
  544.   while sum(DeltaP) > Tolerance:
  545.  
  546.     for i in range(len(P)):
  547.  
  548.       # Try positive delta
  549.  
  550.       P[i] += DeltaP[i]
  551.       Err, Path = Evaluate(P, OtherParams)
  552.  
  553.       if Err < BestErr:
  554.  
  555.         # Positive was good.
  556.  
  557.         BestErr = Err
  558.         DeltaP[i] *= 1.1
  559.         Paths.append(TransformedShape(AM, ShapeFromVertices(Path, 1)))
  560.  
  561.       else:
  562.  
  563.         # Positive delta was bad.
  564.  
  565.         BadPaths.append(TransformedShape(AM, ShapeFromVertices(Path, 1)))
  566.  
  567.         # Try negative delta instead
  568.  
  569.         P[i] -= 2.0 * DeltaP[i]
  570.         Err, Path = Evaluate(P, OtherParams)
  571.  
  572.         if Err < BestErr:
  573.  
  574.           # Negative was good.
  575.  
  576.           BestErr = Err
  577.           DeltaP[i] *= 1.1
  578.           Paths.append(TransformedShape(AM, ShapeFromVertices(Path, 1)))
  579.  
  580.         else:
  581.  
  582.           # Neither positive nor negative was good.
  583.  
  584.           BadPaths.append(TransformedShape(AM, ShapeFromVertices(Path, 1)))
  585.  
  586.           # Try a smaller delta next time.
  587.  
  588.           P[i] += DeltaP[i]
  589.           DeltaP[i] *= 0.9
  590.  
  591.     print "P = %s, |∆P| = %s" % (GFListStr(P), GFListStr(DeltaP))
  592.     #print BestErr
  593.  
  594.   Data['Grid'] = Grid
  595.   Data['Paths'] = Paths
  596.  
  597.   # Uncomment to see the paths rejected by the Greedy Goat.
  598.   #Data['BadPaths'] = BadPaths
  599.  
  600.   return P
  601.  
  602.  
  603. #-------------------------------------------------------------------------------
  604. # Main
  605. #-------------------------------------------------------------------------------
  606.  
  607.  
  608. def Main():
  609.  
  610.   ERRFNIX_STANDARD = 0
  611.   ERRFNIX_ENHANCED = 1
  612.  
  613.   InitialCTE = 1.0
  614.   Tolerance = 0.001
  615.   YScale = 10.0
  616.   ErrFnIx = ERRFNIX_STANDARD
  617.   OutputFileName = 'output.svg'
  618.  
  619.   print 'Initial cross-track error = %g' % (InitialCTE)
  620.   print 'Paramter tolerance = %g' % (Tolerance)
  621.  
  622.   Data = {}
  623.   P = TwiddleAndPlot(InitialCTE, Tolerance, YScale, ErrFnIx, Data)
  624.  
  625.   print 'Best PID paramters:\n' + \
  626.     '  Pprop = %g, Pdiff = %g Pint = %g' % (P[0], P[1], P[2])
  627.  
  628.   print 'Rendering SVG...'
  629.   SVG = RenderToSVG(Data)
  630.   print 'Done.'
  631.  
  632.   print 'Saving SVG to "' + OutputFileName + '"...'
  633.   Save(SVG.encode('utf_8'), OutputFileName)
  634.   print 'Done.'
  635.  
  636.  
  637. #-------------------------------------------------------------------------------
  638. # Command line trigger
  639. #-------------------------------------------------------------------------------
  640.  
  641.  
  642. if __name__ == '__main__':
  643.   Main()
  644.  
  645.  
  646. #-------------------------------------------------------------------------------
  647. # End
  648. #-------------------------------------------------------------------------------
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement