During my morning prep for being found alive again, I recognized an issue with my lovely new scheme. How troubling is it?

Offline

I’ve just finished—modulo a final check—improving all the components so that their update does not refer to their parent, instead only to the start and finish values of their parent, which is passed to them during operation. I did the work offline, which went much faster and saved you from dangerous exposure to tedious but simple changes. We’ll see the code as we work, I imagine.

This morning, at first, I thought I was making a terrible mistake, but now I think it’ll be OK. Let me explain.

The mistake question is: how do we know what start and finish to pass? The thing is: we’ve been passing the start and finish return values from the preceding call to update. But that won’t always work!!!

What the everlasting? Is there some unwritten rule that we must always define our components in immediate parent-child order? What about two components with the same parent? The second one, if we were to proceed naively, would likely receive the start and finish, not from its parent, but from its sibling. We would have to be very careful to save and use the correct pair of values. Consider a sequence like this:

origin = vector(2,3)
center = FixedPoint(origin)
s, f = None, None
s,f = center.update(s, f)
wheel = Wheel(parent=origin, angle_degrees=wheel_radians)
s,f = wheel.update(s, f)
crank = Crank(parent=wheel, radius=radius, lead_degrees=lead_angle)
s,f = crank.update(s, f)
con_rod = ConRod(parent=crank, length=length, piston_angle=piston_angle)
s, f = con_rod.update(s, f)
con_rod_2 = ConRod(parent=crank, length=length_2, piston_angle=piston_angle_2)
s, f = con_rod_2.update(s, f)

Here we have two rods emanating from the same crank. Maybe we have the second one blowing the whistle, or operating the air conditioning. I don’t know: it could happen.

And the code above, so easy and obvious for me to write, is wrong. The con_rod_2 needs the s and f from the crank, not from con_rod.

My plan for the morning had been to start on a Linkage class, a collection class for holding and processing components. Currently, in the main program that does the drawing, the components are kept in a vanilla array and are processed explicitly, such as here in the animation loop:

def animate_linkage(components):
    canvas.scale("all", 0,0, 1/scale, 1/scale)
    s = f = None
    wheel.turn_by(4)
    for component in components:
        s, f = component.update(s, f)
        component.draw(canvas)
    canvas.scale("all", 0,0, scale, scale)
    root.after(30, animate_linkage, components)

If our two ConRod instances were in component, we would have the defect described above, and trust me, bad things would happen.

At first I thought we were doomed, and that having just finished this nice improvement of the components, I had discovered that it was a bad idea, and that I would have to write one of those embarrassing articles about how I messed up yet again. But no. I think it’ll be OK, if we do something like this:

  1. Create a collection class, Linkage, to hold our linkages;
  2. When adding a component to the Linkage, provide its parent explicitly in the add;
  3. Keep track or parents inside the Linkage;
  4. Cache start and finish for each component when it’s updated;
  5. Provide the cached values for a component’s parent when updating the component.

I think that should work and actually be rather good.

We should perhaps begin by removing the parent parameter from all our components, because we are, at least for now, disallowing referring to it. I’ve though, however, that the Linkage could readily be modified to pass one or more parents to a given component if we need them. Or the start/finish for multiples. It’ll all be nicely inside the Linkage and its agreement with the components.

So let’s see about TDDing a Linkage, shall we?

class TestLinkage:
    def test_hookup(self):
        assert False

Test fails. Good news: PyCharm found it. So, I figure our Linkage will understand some kind of add method. In use, I’m not sure that we’ll write an explicit loop outside, iterating it: we’ll have it iterate internally. At least that’s my tentative plan.

Shall I use real components in my tests, or dummy ones? I think real. It’s more, well, real that way.

    def test_exists(self):
        linkage = Linkage()
class Linkage:
    def __init__(self):
        self._components = []

I went too far and added the _components member. Sorry. New test just to play strict for a moment.

    def test_adding(self):
        linkage = Linkage()
        fp = FixedPoint(vector(2,3))
        linkage.add(fp, None)
        components = linkage._components
        assert len(components) == 1

I’m finessing, for now, what the Linkage will have inside it. I’m not sure, but clearly we’ll need the component, its parent, and the eventual start and finish for the component. Should be a tiny object. We might build that right away, having had the thought.

    def add(self, component, parent):
        le = LinkageElement(component, parent)
        self._components.append(le)

Can we have an internal class in Python? I think we can. This code passes:

class Linkage:
    class Element:
        def __init__(self, component, parent):
            self._component = component
            self._parent = parent
            self._start = None
            self._finish = None

    def __init__(self):
        self._components = []

    def add(self, component, parent):
        le = Linkage.Element(component, parent)
        self._components.append(le)

However, those element members are not private. Remove the underbars: I fully intent to manipulate those in Linkage. Still green. Let’s start committing: initial Linkage and Element.

We could enhance the test to check that the _components collection are actually Elements but get real, we can see that they are. The test is mostly just here to express a small step and ensure that we took it. We’re allowed to know what the code is.

Now we’re faced with an important question: what does this Linkage actually do?

Our first use, I imagine, will be to use Linkage in our drawing program. In that situation we have two loops over the components:

for component in components:
    s, f = component.update(s, f)
    component.init_draw(canvas)

for component in components:
    s, f = component.update(s, f)
    component.draw(canvas)

In each case, we want to call a method on the components as they go by. I think there will be more: we might want to use the Linkage in our tests. But this seems to me to be the most common use, and certainly it’s the one we have now. So let’s see how we might do that. We’ll want to provide a function to our Linkage looping method. Let’s try to write a test:

    def test_apply(self):
        def enumerate(component):
            nonlocal count
            count += 1

        linkage = Linkage()
        fp = FixedPoint(vector(2,3))
        linkage.add(fp, None)
        wheel = Wheel(parent=fp)
        linkage.add(wheel, fp)
        count = 0
        linkage.apply(enumerate)
        assert count == 2

With this code, we pass:

    def apply(self, func):
        for element in self._elements:
            func(element)

We’re getting close, but not there yet. We’re applying the function all right, but what we’ll really want to do is to use a function that refers to the component. Our current apply is passing in the entire Linkage.Element, which is not so OK. Furthermore, what about update? We’ll want to update the linkage before any use of it, I should think. Then we might iterate it to draw it, in which case we want the individual elements … but probably really should be wanting their current start and finish values.

We’re close, and we have working tests. Commit: Linkage coming along … It’s all in the test file anyway, so no harm can possibly be done.

It’s Sunday and nearing time for Sunday activities, so we’ll pause here and follow up later today, or, more likely, tomorrow.

See you then!