Small Steps
Let’s keep inching forward, supported by careful tests. I want to get this thing right.
While waking up this morning, I thought I saw the defect in the existing ConRod. I tried my simple fix, and some variations on it, and no joy. Just as well: when we’ve done this right, we’ll be far more happy.
I can’t help wondering whether six decades ago I’d have been able to get this correct in less time. But because I am not allowed to go back on my own time line, we’ll never know. We’ll just go with what we’ve got.
So, where were we? We have a partially-completed test dealing with the origin:
class TestConRodWithOrigin:
def verify(self, v1, v2):
assert v1.x == pytest.approx(v2.x)
assert v1.y == pytest.approx(v2.y)
def test_something(self):
origin = vector(2,3)
theta = 135*math.pi/180
radius = 1
length = 5
start, finish = con_rod_ends(radius, theta, length)
root2by2 = math.sqrt(2)/2
exp_start = vector(-root2by2, root2by2)
exp_x = math.sqrt(24.5) - root2by2
exp_finish = vector(exp_x,0)
self.verify(start, exp_start)
self.verify(finish, exp_finish)
That test is passing but it is not in fact a valid test yet. Because we have an origin at (2,3), the results need to be checked against those raw values:
def test_something(self):
origin = vector(2,3)
theta = 135*math.pi/180
radius = 1
length = 5
start, finish = con_rod_ends(radius, theta, length)
root2by2 = math.sqrt(2)/2
exp_start = vector(-root2by2, root2by2) + origin
exp_x = math.sqrt(24.5) - root2by2
exp_finish = vector(exp_x,0) + origin
self.verify(start, exp_start)
self.verify(finish, exp_finish)
We should get lovely failures now:
Expected :1.2928932188134525 ± 1.3e-06
Actual :-0.7071067811865475
That’s in the verification of the starting value. I’d like to have a bit more of a hint printed. It seems, however, that pytest doesn’t properly handle test descriptions inside functions like my verify. I’ll need to read the fine manual. For now, let’s get down to work.
The test is failing on start.x, because that’s the first thing we check and they’ll all be wrong. What I have in mind for the improvement is to have an outer function that deals with the origin, and have it call con_rod_ends and adjust the result. Like this:
def test_something(self):
origin = vector(2,3)
theta = 135*math.pi/180
radius = 1
length = 5
start, finish = con_rod_ends_with_origin(origin, radius, theta, length)
root2by2 = math.sqrt(2)/2
exp_start = vector(-root2by2, root2by2) + origin
exp_x = math.sqrt(24.5) - root2by2
exp_finish = vector(exp_x,0) + origin
self.verify('start', start, exp_start)
self.verify('finish', finish, exp_finish)
And that function, for now, is nearly trivial, because we don’t use the origin in con_rod_ends at all. So:
def con_rod_ends_with_origin(origin, radius, theta, length):
start, finish = con_rod_ends(radius, theta, length)
return start + origin, finish + origin
And we are green. Commit.
Now this scheme may seem odd, but I am following the general rule of
HardSolution(P) = EasySolution(T*R*P)*R-1*T-1
I’m assuming that a translation is needed and making a space for it. Depending on how things go when we get this working reliably, we may find additional code in this function, or it may be just fine as it is.
Now I think we can start on the rotation. I’ll do a new test class.
Short delay …
I have a test passing, with values that I am confident are no more than a sign away from correct. It’s just one test but it is actually testing a non-zero tilt angle.
def test_at_90(self):
origin = vector(0,0)
wheel_theta = 0
tilt_theta = 90*math.pi/180
radius = 1
length = 5
start, finish = con_rod_ends_complete(
tilt_theta, origin,
radius, wheel_theta, length)
print(f'\n{start=}\n{finish=}')
root24 = math.sqrt(24)
assert start.x == pytest.approx(1)
assert start.y == pytest.approx(0)
assert finish.x == pytest.approx(0)
assert finish.y == pytest.approx(root24)
And the code that passes:
def con_rod_ends_complete(tilt_theta, origin, radius, wheel_theta, length):
use_theta = -tilt_theta + wheel_theta
start, finish = con_rod_ends_with_origin(origin, radius, use_theta, length)
return start.rotate_xy(tilt_theta), finish.rotate_xy(tilt_theta)
I’m not sure, however, whether the signs are right. The question is whether the tilt angle acts as lead or lag. I’ll go draw some pictures. Time for a break anyway.
Things are going well, in the sense that I have code that is working fairly sensibly, and where each operation is isolated enough that I can tell the transforms from the actual work. I’m not loving how slow I’m going, but I have high confidence that we have the problem surrounded,
We’ll assess when it works. See you next time!