My son had to build a drag race car with a supplied electric motor. We wrote this program to determine the correct gear ratio for the car, too low of a gear and the motor would reach its max rpm to soon, too high of a gear and the car would not accelerate as fast. The two validating data points were the winning car with the optimum gear ratio, and my son's car. The predicted times from the program were very close to the actual times.
The heat class is a simple Newton integration of the acceleration and velocity. The delta time increment changes based on the predicted acceleration to give about 50 time increments for the race. The final time increment uses linear interpolation to predict when the finish line is crossed.
I would appreciate your comments on the use of the car and motor classes. From a Lisp background my ideas about attribute slots and accessor methods is a little different then Python. My problem is that the car would ask the motor for the torque, and the motor would need to know the current rpm based on the speed and gear ratio of the car. I tried to make the classes general enough so that you could use a rubber band motor (torque as a function of turns or distance) or a rocket motor (thrust as a function of elapse time).
Also, how would you teach Python to middle and high school students so that they could write a program like this on their own?
Thanks -- JeffSandys
code
# Car Race Calculator
# by Colin Sandys
#
# for Integrated Science
# Mike Dahl, teacher
#
# constants, all values are in metric (gram, centimeter, second)
from math import pi, sqrt # constant pi
one_g = 980.665 # gravity in cm/s^2
# objects
class motor:
# motor default values are based on the 12 volt radio shack motor
def __init__(self, max_t=56., at_rpm=11500., max_rpm=15200., at_volts=12.):
self.rpm = 0. # initialize to zero
self.at_rpm = at_rpm # rpm of maximum torque
self.max_t = max_t # maximum torque in gr-cm
self.max_rpm = max_rpm # maximum rpm no load
self.at_volts = at_volts # voltage for mat_t and max_rpm
self.volts = at_volts # applied voltage
def torque(self, rpm=None, volts=None):
# gr-cm as a function of volts and rpm
if rpm == None: rpm = self.rpm
if volts == None: volts = self.volts
voltage_ratio = volts / self.at_volts
at_rpm = voltage_ratio * self.at_rpm
if rpm < at_rpm:
return voltage_ratio * self.max_t
else:
max_rpm = voltage_ratio * self.max_rpm
rpm_ratio = (max_rpm - rpm) / (max_rpm - at_rpm)
return voltage_ratio * self.max_t * rpm_ratio
# car default values are based on my lego car
class car:
# car default values are based on my lego car
def __init__(self, motor, gear=3., wheel=8., mass=300., max_g=.5):
self.motor = motor # motor object
self.gear = gear # motor rpm at wheel rpm = 1
self.wheel = wheel # diameter in cm
self.mass = mass # weight in gr
self.max_g = max_g # maximum acceleration in gs
self.max_speed = pi * motor.max_rpm * wheel / (gear * 60.)
# calculated max car speed
def speed(self, rpm=None):
# cm/s as a function of motor rpm
if rpm == None: rpm = self.motor.rpm
return pi * self.wheel * rpm / (self.gear * 60.)
def acc(self, speed=None):
# cm/s^2 as a function of car speed cm/s
if speed == None: speed = self.speed()
rpm = 60. * speed * self.gear / (pi * self.wheel)
self.motor.rpm = rpm
force = 2. * self.gear * self.motor.torque(rpm) / self.wheel
if self.max_g < force / self.mass:
return one_g * self.max_g
else:
return one_g * force / self.mass
# race default values are based on the 50 foot race
class heat:
def __init__(self, car, distance=1524., incs = 50):
t = 0. # time in s
x = 0. # car position in cm
v = 0. # car velocity in cm/s
a = 0.
dt0 = distance / (incs * car.max_speed)
dt1 = sqrt(distance / (incs * incs * car.acc(0.)))
dt = dt1
while x < distance:
x0 = x
v0 = v
t0 = t
a0 = a
a1 = car.acc(v)
v1 = v0 + (a0 + a1) * dt / 2.
if v1 > .99 * car.max_speed:
v1 = .99 * car.max_speed
dt = dt0
x1 = x0 + (v0 + v1) * dt / 2.
t1 = t0 + dt
x = x1
v = v1
t = t1
self.final_speed = v0 + (v1 - v0) * (distance - x0)/(x1 - x0)
self.elapse_time = t0 + dt * (distance - x0)/(x1 - x0)
def gear_test(gear_list):
m = motor()
print "Test of different gear ratios by Colin Sandys"
for r in gear_list:
c = car(m, r)
h = heat(c)
print "gear ratio: %2d elapse time: %6.2f trap speed: %4.0f" \
% (r, h.elapse_time, h.final_speed)
gears = [1, 2, 3, 5, 8, 9, 10, 11, 12, 15, 18, 25, 32, 48, 64]
gear_test(gears)
# end of Car CalculatorRun Results
Test of different gear ratios by Colin Sandys gear ratio: 1 elapse time: 11.54 trap speed: 264 gear ratio: 2 elapse time: 8.16 trap speed: 373 gear ratio: 3 elapse time: 6.66 trap speed: 457 gear ratio: 5 elapse time: 5.16 trap speed: 591 gear ratio: 8 elapse time: 4.10 trap speed: 707 gear ratio: 9 elapse time: 3.93 trap speed: 674 gear ratio: 10 elapse time: 3.85 trap speed: 626 gear ratio: 11 elapse time: 3.87 trap speed: 573 gear ratio: 12 elapse time: 4.01 trap speed: 525 gear ratio: 15 elapse time: 4.50 trap speed: 420 gear ratio: 18 elapse time: 5.07 trap speed: 350 gear ratio: 25 elapse time: 6.56 trap speed: 252 gear ratio: 32 elapse time: 8.14 trap speed: 197 gear ratio: 48 elapse time: 11.87 trap speed: 131 gear ratio: 64 elapse time: 15.68 trap speed: 98
comments on code
The code looks clean to me, but I don't fully understand the dt0/dt1 logic. It seems like you could update dt0 every time through the loop to give a smoother integration. Or am I just not following it? -- SteveHowell
dt0 is the delta time increment at maximum car speed, and dt1 for maximum acceleration, for the specified number of increments. We started with a constant delta time increment and had some problems. Cars with low gear ratios accelerate quickly and therefore need a small delta time for accuracy, but they also reach a very low top speed early and the simulations were taking to long.
You are right that adjusting the delta time is a common strategy for dynamic simulation. I wanted to use a straight Newton (rectangular) integration to easily explain this concept to students still taking algebra. Since the electric motor has near constant torque (acceleration) until max rpm, a simple step change of delta time was appropriate. Thank you for the close scrutiny of the code. -- JeffSandys
comments on teaching
This is definitely a challenging program for most middle-school/high-school students. I think one good strategy for integrating Python and physics is to force students to use Python as their calculator.
[showell@localhost foo]$ python Python 1.5.2 (#1, Mar 3 2001, 01:35:43) [GCC 2.96 20000731 (Red Hat Linux 7.1 2 on linux-i386 Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam >>> G = 6.67e-11 >>> m1 = 4e24 >>> m2 = 900 / 9.8 >>> r = 50000 >>> F = G*m1*m2/(r^2) >>> F 490021215478.0 >>> a = F/m2 >>> a 5335786568.54 >>>
You can then build a program around the gravitation law. You'd have a class for the formula. You'd have a method for each unknown variable. All the individual variables would be set by the student. If he sets only three of the five variables, an exception would automatically be thrown.
class GravityCalculator:
def __init__(self):
self.G = 6.67e--11
def F(self):
return self.G * self.m1 * self.m2 / (self.r*self.r)
def m1(self):
rhs = self.G * self.m2 / (self.r*self.r)
return self.F / rhs
def m2(self):
rhs = self.G * self.m1 / (self.r*self.r)
return self.F / rhs
def r(self):
# tricky, let student do algebra on paper first
pass
calc = GravityCalculator()
calc.m1 = 10.
calc.m2 = 15.
calc.r = 20000.
print calc.F()
# If you leave any variables out, you'll get an AttributeError.
# You could catch this exception, but the Python error message is
# actually pretty clear.I'd also like to describe the KarelTheRobot concept to you, Jeff, but I don't have your email. -- SteveHowell
I think to teach Python, you need an application as a wrapper so the students can do something useful early. Logo uses the expression "no threshold, no ceiling", but because Logo does not have object oriented methods Logo now has a ceiling. Python however does have a threshold, and if we want Python to be CP4E then we need to find means to lowering that threshold.
I should mention that I wrote the integration, heat class, portion of the but my son did most of the work on the car and motor classes.
As a past longtime member of Seattle Robotics Society I learned about Karel when it was used to teach Pascal. I am eager to see the work you have done to make Karel the Python Robot. I added my email to my member page, sandysj at asme dot org, and look forward to seeing you at the next meeting. -- JeffSandys