SOFIE visits UC Davis

SOFIE visits UC Davis

Adrian Cooke went to the DSCC 2012 Conference in Florida. After the conference I visited UC Davis in California to visit the Bicycle lab their that has produced some innovative research on bicycles. A brief summary of the knowledge gained there is described in this post, which shows some notes and code from the visit to Davis.

Working with UC Davis bicycle data:

One of the main goals of visiting UC Davis was to work with the data that has been created from
experimentation. The installation and use of this data is now described:

Installing scipy (on Linux, Ubuntu 12.04):

Make sure you have the newest Scipy, Jason likes to punish himself and keep up with the latest bleeding edge technology.

Go to scipy, git clone the repositiory. Open up the installs file and do the following.

 sudo apt-get install python python-dev libatlas3-base-dev gcc gfortran g++
 sudo aptitude install liblapack-dev

Then follow the normal install procedure for python setup.py install…

Installing scipy (on windows):
PythonXY
Enthought

In [1]:
import scipy
scipy.__version__
Out [1]:
'0.12.0.dev-c4dcc00'
In [2]:
import bicycledataprocessor as bdp
In [3]:
dataset = bdp.DataSet()

First time that you open the file you should run this, it only has to be done once to create the initialise the correct tables in the file:

In [4]:
#dataset.open(mode='a')
#dataset.create_task_table()

Then we need to process the data, and get hold of it:

In [5]:
run = bdp.Run('00104', dataset,forceRecalc=True)
Initializing the run object.
Loading metadata from the database.
Loading the raw signals from the database.
Loading the bicycle and rider data for Jason on Rigid Rider
We have foundeth a directory named: /home/cooke/workspace/BicycleDataProcessor/../BicycleParameters/data/bicycles/Rigid.
Found the RawData directory: /home/cooke/workspace/BicycleDataProcessor/../BicycleParameters/data/bicycles/Rigid/RawData
Looks like you've already got some parameters for Rigid, use forceRawCalc to recalculate.
There is no rider on the bicycle, now adding Jason.
Loaded the precalculated parameters from /home/cooke/workspace/BicycleDataProcessor/../BicycleParameters/data/riders/Jason/Parameters/JasonRigidBenchmark.txt
Computing signals from raw data.
The minimun of the error landscape is 0.244489 and the provided guess is 0.240000
Optimization terminated successfully.
         Current function value: 3.070506
         Iterations: 10
         Function evaluations: 20
Optimization terminated successfully.
         Current function value: 3.070485
         Iterations: 11
         Function evaluations: 22
This is what came out of the minimization: [ 0.24279379]
Extracting the task portion from the data.
===============================================================================
Run # 104
Environment: Horse Treadmill
Rider: Jason
Bicycle: Rigid Rider
Speed:3.0
Maneuver: Track Straight Line With Disturbance
Notes: 
===============================================================================
In [6]:
print run
===============================================================================
Run # 104
Environment: Horse Treadmill
Rider: Jason
Bicycle: Rigid Rider
Speed:3.0
Maneuver: Track Straight Line With Disturbance
Notes: 
===============================================================================
In [7]:
run.verify_time_sync()
Out [7]:
In [8]:
run.rawSignals
Out [8]:
{'AccelerationX': RawSignal([      nan,  3.657561,  3.633692, ...,  3.638674,  3.994708,
        3.660625]),
 'AccelerationY': RawSignal([       nan,  0.2808139,  0.376253 , ..., -1.773645 , -1.783415 ,
       -1.621154 ]),
 'AccelerationZ': RawSignal([      nan, -9.272774, -8.456081, ..., -9.246369, -9.099599,
       -9.268735]),
 'AngularRateX': RawSignal([             nan,  -5.63320900e-02,  -5.47738700e-02, ...,
        -2.24847600e-02,  -6.89434500e-03,  -3.65685400e-05]),
 'AngularRateY': RawSignal([        nan,  0.01581983,  0.01266712, ...,  0.00052244,
       -0.00380655, -0.00400798]),
 'AngularRateZ': RawSignal([        nan,  0.01341662,  0.01959944, ..., -0.4061159 ,
       -0.4121288 , -0.4193006 ]),
 'FiveVolts': RawSignal([ 4.98044591,  4.97995267,  4.98061032, ...,  4.97880178,
        4.97995267,  4.98011708]),
 'FrameAccelX': RawSignal([ 1.47268793,  1.47334558,  1.47120821, ...,  1.47170145,
        1.46561817,  1.47449648]),
 'FrameAccelY': RawSignal([ 1.83867164,  1.84722113,  1.8419599 , ...,  1.84557699,
        1.84541258,  1.8643201 ]),
 'FrameAccelZ': RawSignal([ 1.5761038 ,  1.56262192,  1.57577498, ...,  1.58531094,
        1.60536935,  1.59747751]),
 'MagX': RawSignal([      nan,  1.10222 ,  1.100719, ...,  1.262892,  1.261871,
        1.261935]),
 'MagY': RawSignal([       nan,  0.5896677,  0.5897546, ...,  0.4864934,  0.4874919,
        0.4897254]),
 'MagZ': RawSignal([       nan, -0.3653959, -0.3641196, ..., -0.3035113, -0.3049648,
       -0.3069487]),
 'PullForceBridge': RawSignal([ 5.51558331,  5.51656958,  5.51558331, ...,  5.51689834,
        5.51755585,  5.51788461]),
 'PushButton': RawSignal([  4.97649999e+00,   4.97649999e+00,   4.97633557e+00, ...,
         1.84891819e-03,   9.90515113e-03,  -4.23435969e-03]),
 'RollPotentiometer': RawSignal([ 2.49336511,  2.48859712,  2.48876154, ...,  2.50832671,
        2.50602493,  2.50405197]),
 'SteerPotentiometer': RawSignal([ 2.3157988 ,  2.31760734,  2.31925147, ...,  1.87665109,
        1.89111945,  1.90723194]),
 'SteerRateGyro': RawSignal([ 2.47478641,  2.46952518,  2.47626613, ...,  2.19363976,
        2.15878416,  2.13494424]),
 'SteerTorqueSensor': RawSignal([-0.83041416, -0.80838747, -0.78504575, ..., -2.29633901,
       -2.26970975, -2.26543592]),
 'Temperature': RawSignal([      nan,  317.8788,  317.8788, ...,  317.9444,  317.9444,
        317.9445]),
 'ThreeVolts': RawSignal([ 3.14904532,  3.14871649,  3.14822325, ...,  3.14855208,
        3.1488809 ,  3.14871649]),
 'WheelSpeedMotor': RawSignal([ 2.25168109,  2.25858498,  2.2546399 , ...,  2.21157272,
        2.2523386 ,  2.20401131])}
In [9]:
run.rawSignals['SteerRateGyro'].units
run.rawSignals['AccelerationY'].units
Out [9]:
'meter/second/second'
In [10]:
run.calibratedSignals
Out [10]:
{'AccelerationX': RawSignal([      nan,  3.657561,  3.633692, ...,  3.638674,  3.994708,
        3.660625]),
 'AccelerationY': RawSignal([       nan,  0.2808139,  0.376253 , ..., -1.773645 , -1.783415 ,
       -1.621154 ]),
 'AccelerationZ': RawSignal([      nan, -9.272774, -8.456081, ..., -9.246369, -9.099599,
       -9.268735]),
 'AccelerometerAccelerationX': Signal([-3.3299956 , -3.30311405, -3.3649416 , ..., -3.35418899,
       -3.55848877, -3.26547988]),
 'AccelerometerAccelerationY': Signal([ 8.63767194,  8.92261645,  8.75863896, ...,  8.8715415 ,
        8.86078887,  9.48175285]),
 'AccelerometerAccelerationZ': Signal([ 0.05170346, -0.38377767,  0.05439163, ...,  0.36084132,
        1.01137487,  0.75600013]),
 'AngularRateX': RawSignal([             nan,  -5.63320900e-02,  -5.47738700e-02, ...,
        -2.24847600e-02,  -6.89434500e-03,  -3.65685400e-05]),
 'AngularRateY': RawSignal([        nan,  0.01581983,  0.01266712, ...,  0.00052244,
       -0.00380655, -0.00400798]),
 'AngularRateZ': RawSignal([        nan,  0.01341662,  0.01959944, ..., -0.4061159 ,
       -0.4121288 , -0.4193006 ]),
 'FiveVolts': RawSignal([ 4.98044591,  4.97995267,  4.98061032, ...,  4.97880178,
        4.97995267,  4.98011708]),
 'ForkRate': Signal([-0.02693987, -0.03569136, -0.02450093, ..., -0.51616254,
       -0.57799684, -0.61974575]),
 'MagX': RawSignal([      nan,  1.10222 ,  1.100719, ...,  1.262892,  1.261871,
        1.261935]),
 'MagY': RawSignal([       nan,  0.5896677,  0.5897546, ...,  0.4864934,  0.4874919,
        0.4897254]),
 'MagZ': RawSignal([       nan, -0.3653959, -0.3641196, ..., -0.3035113, -0.3049648,
       -0.3069487]),
 'PullForce': Signal([ 0.50915384,  0.52900773,  0.50915384, ...,  0.53562569,
        0.54886161,  0.55547957]),
 'PushButton': RawSignal([  4.97649999e+00,   4.97649999e+00,   4.97633557e+00, ...,
         1.84891819e-03,   9.90515113e-03,  -4.23435969e-03]),
 'RearWheelRate': Signal([ 9.34469315,  9.37338086,  9.35698788, ...,  9.17803125,
        9.34742531,  9.14661139]),
 'RollAngle': Signal([ 0.43753619,  0.14035059,  0.12955648, ...,  1.47536364,
        1.28594489,  1.15082085]),
 'SteerAngle': Signal([  2.45804995,   2.41625603,   2.38881799, ...,  11.4506724 ,
        11.16284592,  10.83369557]),
 'SteerTubeTorque': Signal([-12.50940189, -12.17864079, -11.82813274, ..., -34.52228414,
       -34.12240913, -34.05823166]),
 'Temperature': RawSignal([      nan,  317.8788,  317.8788, ...,  317.9444,  317.9444,
        317.9445]),
 'ThreeVolts': RawSignal([ 3.14904532,  3.14871649,  3.14822325, ...,  3.14855208,
        3.1488809 ,  3.14871649])}
In [11]:
run.truncatedSignals
Out [11]:
{'AccelerationX': Signal([ 2.47077461,  3.657561  ,  3.633692  , ...,  3.211428  ,
        3.133757  ,  2.965047  ]),
 'AccelerationY': Signal([ 0.32030702,  0.2808139 ,  0.376253  , ..., -1.591935  ,
       -1.481616  , -1.783708  ]),
 'AccelerationZ': Signal([-9.02043158, -9.272774  , -8.456081  , ..., -9.536515  ,
       -9.220658  , -9.00966   ]),
 'AccelerometerAccelerationX': Signal([-4.00748813, -4.00464501, -3.97744755, ..., -3.48347678,
       -3.46834316, -3.39476767]),
 'AccelerometerAccelerationY': Signal([ 9.30015633,  8.97047881,  8.38874647, ...,  9.09809172,
        8.86553338,  9.20775755]),
 'AccelerometerAccelerationZ': Signal([-1.84970437, -1.80003605, -1.99404212, ...,  0.01093398,
        0.72433224,  0.86868216]),
 'AngularRateX': Signal([-0.09674107, -0.05633209, -0.05477387, ..., -0.4765857 ,
       -0.4696014 , -0.4629069 ]),
 'AngularRateY': Signal([ 0.05289091,  0.01581983,  0.01266712, ...,  0.03231768,
        0.02531269,  0.02697365]),
 'AngularRateZ': Signal([ 0.02313052,  0.01341662,  0.01959944, ...,  0.3932629 ,
        0.3801239 ,  0.3574072 ]),
 'FiveVolts': Signal([ 4.9801364 ,  4.98018963,  4.98011708, ...,  4.9793096 ,
        4.97944485,  4.98004454]),
 'ForkRate': Signal([ 0.2198569 ,  0.21641052,  0.21563719, ..., -0.48229508,
       -0.55071296, -0.60132438]),
 'MagX': Signal([ 1.09781256,  1.10222   ,  1.100719  , ...,  1.220719  ,
        1.222653  ,  1.218221  ]),
 'MagY': Signal([ 0.58482581,  0.5896677 ,  0.5897546 , ...,  0.559401  ,
        0.5539194 ,  0.5525444 ]),
 'MagZ': Signal([-0.36923584, -0.3653959 , -0.3641196 , ..., -0.3147601 ,
       -0.3151211 , -0.3126837 ]),
 'PullForce': Signal([ 0.52238977,  0.51869193,  0.51946964, ...,  0.54146593,
        0.54302137,  0.55255945]),
 'PushButton': Signal([  4.97620506e+00,   4.97630167e+00,   4.97638880e+00, ...,
         1.05091372e-03,   6.35040393e-03,   2.00458434e-03]),
 'RearWheelRate': Signal([ 9.55659323,  9.44547727,  9.4245589 , ...,  9.34198529,
        9.27268156,  9.2352189 ]),
 'RollAngle': Signal([ 0.1222388 ,  0.11095325,  0.10729716, ...,  1.48238981,
        1.36952437,  1.21044323]),
 'SteerAngle': Signal([  0.97649343,   1.05862673,   1.12049643, ...,  11.58701244,
        11.28984701,  10.97893048]),
 'SteerTubeTorque': Signal([  7.34323336,   7.32655139,   7.11486284, ..., -34.6812994 ,
       -34.29885072, -34.08654945]),
 'Temperature': Signal([ 317.8788,  317.8788,  317.8788, ...,  317.9431,  317.9431,
        317.9432]),
 'ThreeVolts': Signal([ 3.14897277,  3.14913718,  3.14875039, ...,  3.14847953,
        3.14873581,  3.14878903])}
In [12]:
run.computedSignals
Out [12]:
{'FiveVolts': Signal([ 4.9801364 ,  4.98018963,  4.98011708, ...,  4.9793096 ,
        4.97944485,  4.98004454]),
 'ForwardSpeed': Signal([-3.17783509, -3.14088592, -3.13392997, ..., -3.10647194,
       -3.08342651, -3.07096912]),
 'PitchRate': Signal([ 0.05277959,  0.01576109,  0.01260023, ...,  0.01879908,
        0.01317432,  0.01674585]),
 'PullForce': Signal([ 2.32370545,  2.30725664,  2.3107161 , ...,  2.40856046,
        2.41547939,  2.4579069 ]),
 'PushButton': Signal([  4.97620506e+00,   4.97630167e+00,   4.97638880e+00, ...,
         1.05091372e-03,   6.35040393e-03,   2.00458434e-03]),
 'RearWheelRate': Signal([ 9.55659323,  9.44547727,  9.4245589 , ...,  9.34198529,
        9.27268156,  9.2352189 ]),
 'RollAngle': Signal([ 0.00213347,  0.0019365 ,  0.00187269, ...,  0.02587258,
        0.02390271,  0.02112622]),
 'RollRate': Signal([-0.08468524, -0.04932831, -0.0459178 , ..., -0.32999739,
       -0.32746391, -0.32819573]),
 'SteerAngle': Signal([ 0.01704303,  0.01847652,  0.01955635, ...,  0.20223152,
        0.197045  ,  0.19161849]),
 'SteerRate': Signal([ 0.19672639,  0.2029939 ,  0.19603775, ..., -0.87555798,
       -0.93083686, -0.95873158]),
 'SteerTorque': Signal([ 0.61580208,  0.60018495,  0.62660908, ..., -4.88180098,
       -4.64762224, -4.40787578]),
 'ThreeVolts': Signal([ 3.14897277,  3.14913718,  3.14875039, ...,  3.14847953,
        3.14873581,  3.14878903]),
 'YawRate': Signal([ 0.05217546,  0.03033202,  0.03571939, ...,  0.52256494,
        0.50787241,  0.4841641 ])}

Specific to your maneuver:

The descriptions of the maneuvers are described in the Systems Identification part of the thesis.

Applies logic specific to different tasks: To get hold of the heading on a straight line or after a maneuver.

In [13]:
run.taskSignals
Out [13]:
{'FiveVolts': Signal([ 4.98017031,  4.98004454,  4.98011708, ...,  4.98009776,
        4.9793096 ,  4.97944485]),
 'ForwardSpeed': Signal([-3.08804774, -3.07821709, -3.09392892, ..., -3.14504474,
       -3.10647194, -3.08342651]),
 'LateralFrontContact': Signal([ 0.04783112,  0.04799592,  0.04817749, ...,  0.16907404,
        0.17046786,  0.17169972]),
 'LateralRearContact': Signal([ 0.05313131,  0.05314765,  0.05316148, ...,  0.05693038,
        0.05516465,  0.05337492]),
 'LateralRearContactRate': Signal([ 0.0147595 ,  0.01421775,  0.01375049, ..., -0.36196549,
       -0.36609941, -0.37157731]),
 'LongitudinalFrontContact': Signal([  1.06377034,   1.04834531,   1.03291666, ..., -89.22539833,
       -89.24148688, -89.25750375]),
 'LongitudinalRearContact': Signal([  0.00000000e+00,  -1.54154918e-02,  -3.08456983e-02, ...,
        -9.02882936e+01,  -9.03038160e+01,  -9.03191805e+01]),
 'LongitudinalRearContactRate': Signal([-3.08801247, -3.07818425, -3.09389836, ..., -3.12414587,
       -3.084824  , -3.06095562]),
 'PitchRate': Signal([ 0.0025994 ,  0.00428815,  0.01215716, ...,  0.017302  ,
        0.01879908,  0.01317432]),
 'PullForce': Signal([ 2.39557112,  2.39903058,  2.41201993, ...,  2.45444743,
        2.40856046,  2.41547939]),
 'PushButton': Signal([-0.01183782,  0.00048039,  0.00078989, ..., -0.00503783,
        0.00105091,  0.0063504 ]),
 'RearWheelRate': Signal([ 9.28657885,  9.25701546,  9.30426511, ...,  9.45798395,
        9.34198529,  9.27268156]),
 'RollAngle': Signal([-0.00360557, -0.01114409, -0.00998905, ...,  0.0262769 ,
        0.02587258,  0.02390271]),
 'RollRate': Signal([-0.01591406, -0.00859876, -0.0129135 , ..., -0.32698925,
       -0.32999739, -0.32746391]),
 'SteerAngle': Signal([ 0.00378915,  0.00418438,  0.00449817, ...,  0.2074194 ,
        0.20223152,  0.197045  ]),
 'SteerRate': Signal([ 0.08357694,  0.08925328,  0.08380447, ..., -0.83027144,
       -0.87555798, -0.93083686]),
 'SteerTorque': Signal([ 0.31410154,  0.23392595,  0.04609619, ..., -4.98081321,
       -4.88180098, -4.64762224]),
 'ThreeVolts': Signal([ 3.14913718,  3.14939346,  3.14898735, ...,  3.14846021,
        3.14847953,  3.14873581]),
 'YawAngle': Signal([-0.00477958, -0.00461884, -0.00444436, ...,  0.11534634,
        0.11812506,  0.12080151]),
 'YawRate': Signal([ 0.01667843,  0.01786812,  0.02217558, ...,  0.54878437,
        0.52256494,  0.50787241])}
In [16]:
run.plot?
In [15]:
run.plot('RollRate')
Out [15]:

SOFIE HDF FORMAT and Jasons Data:

We would like to use the data that Jason created in our project to validate our model and design our controllers.

Thus we would like to access the data from Matlab. Jason uses PyTables for the data storage and SOFIE HDF FORMAT used the same format. We have a matlab chain to read this data into Matlab and a driver for Jason’s data format was also created. It can be found in the bdpReader.m.

Control Model:

The second main goal of visiting Davis was to get information about how the control model works that (Jason Moore and Ronald Hess developed)[ https://ieeexplore.ieee.org/xpl/login.jsp?tp=&arnumber=6151180 ].

The model is based on ideas developed by Hess, which is based on emperical experiences of what a good controller for an aircraft should be:

  • The crux of the problem is that there should be a 10dB peak in the shape of the bode plots for the transfer function described in the paper mentioned above.
  • Then the cross over frequencies for the other poles should sit at 2Hz, 1 Hz and 0.5Hz, which are values obtained for aircraft control. In the regions close to the cross over frequency, the bode plots should be linear.

Specific gains are determined using ‘black magic’ for the transfer function for each forward speed, (and this black magic is then alogrithmically approximated with the generate_data.m scripts). Thus for each speed there are different gains for the control model, these can be created before hand and then used as a look-up table during the simulation. The model is a linear model, this implies that there are only lateral dynamics. The forward speed does not get effected.

Dissertation:

The main source for information about the model is found in Jason’s dessertation.
Control Chapter – Source file generates the gains. choose_gains.m.

HumanControl

Entire model is created using simulink.

Can change the model used (A,B,C,D) for the calulations. Not sure if it works for higher order systems.

generate_data.m- Tries to implement Ron Hess’s method for obtaining the gains. So use the ball park gains from the plots, and this funciton tries to find better values.

  • Estimates the Gains
  • Plots controller transfer functions and bode plots.
  • Simulate different maneavers.

The method also proposes a Handling quality metric, which is based on the idea’s from aircraft control – only valid at $2frac{rad}{s}$ open loop crossover frequency for the roll loop. Untested hypothesis, so not sure about it yet.

At the DSCC conference there was a dicussion about how handling is described for different vehicles. A person their stated that one of the ways it is done in aircraft analysis is the degree of uniformity between movements in the different directions. What this means is that a pilot should apply the same amount of ‘effort’ to move in the roll, pitch and yaw directions when controlling an aircraft. Possibly there is the same sort of characteristic for bicycle control, lean control vs steering control?

ieee.m – Make the ieee plots.

The control model does not work at lower speed: 1.5 m/s.

Simulink is used for the model which Jason says has certain implications:

  • Creates a nightmare if you want to test different parameters.
  • Simulink creates variables in the global namespace, thus creates problems with complex operations.

System Indentification:

Noice needs to be modeled better, could only get to local minima and not globals. More works needs to be done in this analysis.

The UC Davis Lab:

I spent a lot of time in the lab just taking a look to see how they do things. Luke and Jason
both have fully instrumented bicycles with different goals. Lukes is to create a fully
automated bicycle for experiments and Jason’s is for analysing user behaviour on the bicycle. Luke is still creating the final test bike and Jason is finished with his PHD.

Lukes Bicycle:

A well designed bicycle to perform automated tests. The bicycle rides for itself and is going to be used to perform various experiments on bicycles and how the dynamics works.

Sensors:

  • Steering torque: There is a direct drive DC motor connected to the steering column to
    provide steering functionality. It has a 16000 position encoder that is used to calculate
    the steering angle and rate.
  • Front and rear wheel speeds: Encoders geared down to get high resolution.
  • Rear wheel motor has a controller which has feed back from the roll rate and forward speed.
  • Kalman filter used in the control system.
  • Large battery for the motor.
  • Small battery for the electronics – battery condition monitored by the embedded board.
  • Triple axis acclerometer used for rate calculations.

Management:

  • A embedded ARM chip from ST used as the control system. ChibiOS running on the Chip with XBee
    communication to the outside world.
  • OS updated on the chip using JTAG.
  • Low-level embedded design of the system.

Jasons Bicycle:

Sensors:

  • Different batteries for the different systems, laptop, rear wheel motor and electronics.
  • Rear wheel motor: A power controller connected. No feedback.
  • Velocity: DC motor, callibrated with known rotation speed and test along hallway to
    estimate the rolling circumference of the wheel.
  • Steer angle: Potentiometer – callibrated by adding a angle measurement thing to the bottom
    of the head tube and measured against the frame.
  • Steering Rate: Rate Gyro – un callibrated.
  • Steer Torque: Hand force sensors on the handlebar. Mark Everste at Delft did it. The steering torque is always smaller than 5n.m.
    Jasons steer torque is in the head tube – Zero backlash double universal joint to isolate the rotational
    torques. A Futek sensor used to measure the forces. It has a slip shut to protect the futek sensor.
  • Sync button on handlebar.
  • Roll angle trailer. Not geared down. worked well on smooth surfaces, not enough resolution.
  • Pertubation device: 150 n.m load cell, with an amplifier connects to NI box.
  • IMU has an accelerometer connected to it for synchronisatoin. Riding over a known bump to create the synchronisation signal.

Management:

  • Small laptop on bicycle running MATLAB for data collection.
  • GUI written in matlab that starts the IMU and NI data logger. Easy to use for non-tech people.

Useful technology that Jason, Luke and the team introduced me to:

VirtualENV:

A great tool to manage your python installation for different projects. I have seen it before, but not used it because it seemed complicated to get going, but with the wrapper its easy to use.

Install virtualenv and virtualenv-wrapper.

Another useful tip is to used ‘python setup.py develop’ if you are creating your own package and would like it accessible from the system.

PyDy: SimPy

Symbolic dynamics library developed at Davis using the Kanes method and also Lagrange’s method.

PyDy

Ipython and ipython notebook

Yes these are fun things to use, you can make documents with code embedded in the docs, which can execute on screen.

Blogging on Ipython:

This post was written using IPython notebook. A very useful tool where you can write text and
run python code in your browser.

It was converted to a static HTML page using.
Post about blogging