Code style query: multiple assignments in if/elif tree

Chris Angelico rosuav at gmail.com
Tue Apr 1 01:01:40 EDT 2014


On Tue, Apr 1, 2014 at 3:26 PM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Tue, 01 Apr 2014 01:33:09 +1100, Chris Angelico wrote:
>
>> Call this a code review request, if you like. I'm wondering how you'd go
>> about coding something like this.
>
> I wouldn't. I'd start off by analysing the problem, and putting it into
> the simplest format possible, and *then* start writing code if and only
> if needed. See below.
>
> The first mistake of computational mathematics is to do the computation
> before the mathematics, and the Holy Grail is to avoid the computation
> altogether.

Fair enough. :)

>> Imagine you're in a train, and the brakes don't apply instantly. The
>> definition, in the interests of passenger comfort,
>
> Ah, you're using comfort in the modern sense, i.e. what people used to
> call discomfort :-P
>
> The scenario you describe has (effectively) infinite rate-of-change-of-
> acceleration, often called "jerk". (A jerk is a rapid change in
> acceleration.) Human comfort is (within reasonable limits) more affected
> by jerk than acceleration. The passengers will feel three quite
> distinctive jerks, one when the brakes are first applied (which is
> probably reasonable), then one at 1s, then again at 2s. That's not
> comfortable by any stretch of the imagination.

It actually is a smooth increase in deceleration, but I'm operating
the simulator on a 1s period, so it's actually an average across the
first second, and an average across the next second...

>> is that the first
>> second of brake application has an acceleration of 0.2 m/s/s, the next
>> second has 0.425 m/s/s, and thereafter full effect of 0.85 m/s/s.

... so really, it's seeking upward from 0 to 0.85 according to some
curve, the details of which I'm not familiar with, but in terms of 1s
average accelerations, it's those figures. Actually even 1s resolution
is more than we need; the overall purpose is to get a calculated time
in minutes, so calculating to sub-second resolution (when, in reality,
this is trying to predict what a human will do) is massive overkill.

>> You
>> have a state variable that says whether the brakes have just been
>> applied, have already been applied for at least two seconds, or haven't
>> yet been applied at all.
>
> I wouldn't model it that way. Especially since you've missed at least one
> state :-) I'd model the acceleration as a function of time, otherwise you
> have to convert a time into a state. Nevertheless, if you insist, we can
> use a state variable instead:
>
>           0 for state == "BRAKES NOT APPLIED YET"
> accel = { 0.2 for state == "BRAKES ON FOR BETWEEN 0 AND 1 SECOND"
>           0.425 for STATE == BRAKES ON FOR BETWEEN 1 AND 2 SECOND"
>           0.85 for state == "BRAKES ON FOR MORE THAN 2 SECONDS"
>
> Implied, but not stated, is that once the train stops, acceleration also
> goes to zero -- the train does not start moving backwards.

"Just been applied" is poorly named; that's the state where the brakes
have been applied for 1 second so far. The other state (brakes not
applied yet) could be looking at powering (which follows a similar
curve in the opposite direction) or cruising (which has acceleration
of 0.0 m/s/s).

> Haskell has nifty pattern-matching syntax for this that looks quite close
> to the mathematical hybrid function syntax, but in Python, we're limited
> to explicitly using an if. If I were coding this, and I'm not, I'd wrap
> it in a function. One advantage of a state variable rather than a
> continuous time function is that we can do this:
>
> def accel(state):
>     return {NO_BRAKING: 0.0,
>             LOW_BRAKING: 0.2,
>             MID_BRAKING: 0.425,
>             HIGH_BRAKING: 0.85}[state]

That could be done, but with .get() to allow a default. I may well do that.

>> Problem: Work out how far you'll go before the
>> brakes reach full power, and how fast you'll be going at that point.
>
> Given that problem, we actually only care about LOW_BRAKING and
> MID_BRAKING. The problem becomes quite simple:

Right. The other state is "zero distance and zero loss of speed".

> At t=0, the train is travelling at u m/s and the brakes are applied with
> acceleration of 0.2m/s^2 for one second, then 0.425m/s^2 for an
> additional one second. What is the speed of the train after those two
> seconds, and the distance travelled.
>
> This becomes a simple question for the four standard equations of motion:
>
> (1) v = u + at
> (2) s = 1/2(u + v)t
> (3) s = ut + 1/2(at^2)
> (4) v^2 = u^2 + 2as
>
> Only (1) and (3) are needed.

Okay, what's u here? Heh.

> The distance travelled in the first second is:
>
>     s1 = u - 0.1  m
>
> and the speed of the train at that time becomes:
>
>     v = u - 0.2  m/s
>
> Applying this for the second second gives:
>
>     s2 = (u - 0.2) - 0.2125  m
>
>     v = (u - 0.2) - 0.425  m/s
>
> and the total distance:
>
>     s = s1 + s2
>
>
> No programming required, just a calculator :-)
>
> Given that solving the entire problem is barely five lines, writing code
> to do so is probably unnecessary.

This is one small part of a larger piece of code (which, in this
instance, is trying to answer this question: "If I apply the brakes
now and keep them on until the next curve, will I be way below the
curve's speed limit?" - if yes, don't apply the brakes now).

> The only thing I haven't done is check that the train isn't travelling so
> slowly that it comes to a complete halt within two seconds. I leave that
> as an exercise. (Hint: if the final speed is negative, the train came to
> a halt.)

Yeah. Or just let that go into the next part of the calculation, which
works out quadratically how much distance -> how much time -> how much
remaining speed, which may have no solution ie it doesn't reach that
point. That part's fine.

> Whatever you used to post this, ate the indentation.

I posted that part without indentation, yeah. Gmail eats tabs.
Normally I manually replace them with spaces for posting, but since
this whole section was at the same indentation level, I didn't bother.
It is one of the problems with Gmail, though, and if I weren't running
this account on multiple systems of different OSes, I'd probably use a
dedicated client.

>> # Already got the brakes fully on
>> if mode=="Brake2": distance_to_full_braking_power, speed_full_brake =
>> 0.0, curspeed
>> # The brakes went on one second ago, they're nearly full elif
>> mode=="Brake1": distance_to_full_braking_power, speed_full_brake =
>> curspeed - 0.2125, curspeed - 0.425 # Brakes aren't on.
>> else: distance_to_full_braking_power, speed_full_brake = (curspeed -
>> 0.1) + (curspeed - 0.4125), curspeed - 0.625 # If we hit the brakes now
>> (or already have hit them), we'll go another d meters and be going at s
>> m/s before reaching full braking power.
>
> Using sequence unpacking just to save a newline is naughty :-)
>
> dist, speed = (curspeed - 0.1) + (curspeed - 0.4125), curspeed - 0.625
>
> is an abuse of syntax, not far removed from:
>
> dist = (curspeed - 0.1) + (curspeed - 0.4125); speed = curspeed - 0.625
>
> It will be much more comprehensible written as two statements.

Fair enough. :) That's why I asked. Is it naughty enough to break into
two statements, or is it better to combine it into a single multiple
assignment?

>> But I don't like the layout. I could change it to a single assignment
>> with expression-if, but that feels awkward too. How would you lay this
>> out?
>>
>> (Note that the "else" case could have any of several modes in it, so I
>> can't so easily use a dict.)
>
> It could have, but doesn't. Is your aim to build a general purpose
> Newtonian linear motion solver, or to solve this one problem?

To solve this one problem, as part of a simulator. For any purpose
beyond what you can see in the code itself, you'd have to ask my
brother; I don't have the full details on that.

Thanks for the advice! Flamage welcome, I'm wearing the asbestos
that's just been removed from the theatre here...

ChrisA



More information about the Python-list mailing list