# C[omp]ute

Welcome to my blog, which was once a mailing list of the same name and is still generated by mail. Please reply via the "comment" links.

Always interested in offers/projects/new ideas. Eclectic experience in fields like: numerical computing; Python web; Java enterprise; functional languages; GPGPU; SQL databases; etc. Based in Santiago, Chile; telecommute worldwide. CV; email.

© 2006-2015 Andrew Cooke (site) / post authors (content).

## [Cycling, Computing] Power Calculation and Brakes

From: andrew cooke <andrew@...>

Date: Sat, 24 Apr 2021 16:20:34 -0400

It's common (eg Strava) to estimate a cyclist's power output from their GPS
and elevation (barometric) data.

The basic idea is fairly simple: we can calculate the power required for the
bicycle to behave as recorded at each point in time using mass / gravity /
height, CdA / speed, and rolling resistance / speed.

This ignores wind, which makes the effective speed for wind resistance (CdA)
different from the measured speed, and so introduces errors.

Also ignored, but not commonly discussed, is braking.

If the cyclist does not use the brakes then all we have discussed so far is
correct.

If the cyclist brakes *and* pedals then we will underestimate their power
output because they are expending additional energy we do not "see" in the
data (assuming that there is no power meter data).

If the cyclist brakes when not pedalling then we *should* (if we have
sufficient resolution, perfect parameters, etc) see a negative input power.

In practice, when you estimate power, you do see negative spikes.  I had
assumed that these were noise, but the above suggests that it is also
reasonable to interpret them as braking.

In conclusion, then, braking may be visible in the output from modelling and
is not inherently a source of uncertainty *unless* the cyclist brakes when
also pedalling.  That exception is more likely than it sounds since "when
also" does not, in practice, mean (only) concurrently, but also at any time
within the "time step" used in the calculation (eg 10s).

Andrew

## Previous Entries

### [Hardware, Computing] Amazing Pockit Computer

From: andrew cooke <andrew@...>

Date: Thu, 18 Mar 2021 21:02:15 -0300

I am not sure I really believe this is real.  https://pockit.ai/

Watch the top video in the timeline.

Andrew

### Bullying

From: andrew cooke <andrew@...>

Date: Thu, 18 Mar 2021 04:59:59 -0300

I was buliied recently.  I am a 53 year old, white, male, successful
professional.  It was a bit weird.

I don't want to give details, because they would identify the people involved,
which would be complicated socially.  But I was harassed by email over a
couple of weeks by two people, who also contacted my partner, Paulina.

At first it just seemed like odd behaviour, but when I asked them to stop, and
they did not, I recognised the similarities with episodes in childhood.  Like
then I was confused but resistant (so you might call it an attempt at
bullying, rather than the real thing, because I never felt intimidated).  Like
then, the people involved were - I think (I can find no other explanation) -
resentful that I have a better life than they do.

There's really no word other than weird.  It was damn weird.

They apologised later and we haven't communicated since.

Andrew

### How I Am - 3 Years Post Accident, 8+ Years With MS

From: andrew cooke <andrew@...>

Date: Fri, 26 Feb 2021 21:28:47 -0300

Previously

https://acooke.org/cute/MyBikeAcci0.html
https://acooke.org/cute/HowIAm80.html
...
http://acooke.org/cute/HowIAm0.html

My back was fixed by yoga and a harder mattress.  The legs (no idea what was
wrong with the right) have now healed enough that I am generally able to sleep
on my side (although not always after exercise - sometimes it is more
comfortable to switch to the softer sofa).  There can be some discomfort (not
pain) on the sides of the legs when walking and the tendon at the back of my
left leg (which interfered with the rod, I think) can get a little sore.

Recovering fitness post-accident has taken longer than expected.  Covid didn't
help - there was a time when we were allowed out for just a few hours twice a
week (time spent shopping, mainly).  I am now, almost three years on, capable
of climbing to Yerba Loca, but not to Farellones.  My power and endurance are
still not as they were - I suspect it will be another 6 months work.  On the
other hand, Yoga on days off the bike seems to have improved my core.

MS has been pretty steady for years.  In fact, a few months ago I was feeling
like I had 6 months almost symptom-free.  Unfortunately that ended with
intermittent "white noise" in my right leg, the muscle above the knee.  This
has been coming and going a month or more now - it is disturbing, but seems to
have no effect on strength or balance.

In a few weeks I may be due for the Covid vaccine.  This will likely be
Sinovac and my doctor is a little worried (something to do with the underlying
carrier and my borked immune system, not the Covid part).  We will see what
happens.

We've spent, what, maybe a year pretty much isolated.  Paulina is working from
home.  We go out only to buy supplies or exercise.

Hello to future self.  Good luck.

Andrew

### [USA Politics] In America's Uncivil War Republicans Are The Aggressors

From: andrew cooke <andrew@...>

Date: Mon, 8 Feb 2021 16:48:13 -0300

An important article from Perry Bacon Jr.

https://fivethirtyeight.com/features/in-americas-uncivil-war-republicans-are-the-aggressors/

Andrew

### [Programming] Selenium and Python

From: andrew cooke <andrew@...>

Date: Sun, 24 Jan 2021 09:14:51 -0300

I am testing user registration (including email confirmation) and it's this
easy:

def test_registration_flow(self):

with self.record_error():

self.driver.get(self.url)

with self.smtpd():
self.driver.find_element_by_id('email').send_keys('andrew@...')
self.driver.find_element_by_css_selector('input.btn').click()

match = re.compile(r'(http[:/a-zA-Z0-9\-_]+)', re.MULTILINE).search(self.email)
self.driver.get(match.group(1))

self.assertTrue('Your email has been confirmed.' in self.driver.page_source)
self.assertTrue('You have signed in successfully.' in self.driver.page_source)

with self.app_context():
users = User.query.all()
self.assertEqual(1, len(users))
user = users[0]
self.assertEqual('andrew@...', user.email)
self.assertEqual(dt.date.today(), user.email_confirmed_at)
self.assertTrue(user.active)

Obvs there's some infrastructure in there (smtpd, etc), but I still find it
amazing that a browser starts, email is sent, and the user is registered.

Also, if an error occurs, I leave the database on disk and the browser open,
to make debugging easier.

Andrew

### [Bike] How Fast Before Walking More Efficient Than Cycling?

From: andrew cooke <andrew@...>

Date: Thu, 21 Jan 2021 22:52:02 -0300

Cycling is generally more efficient, per km travelled, than walking.  However,
someone just pointed out to me that if you go fast enough this is not the case
(since losses to air resistance increase rapidly with velocity).

So what velocities are we talking about?

A typical walking speed is 3.5 mph.  That's roughly 1.5 m/s.  Looking at
http://sprott.physics.wisc.edu/technote/walkrun.htm that correponds to roughly
300 W.

So walking burns 300 / 1.5 J/m = 200 J/m.

Now let's consider cycling.  A typical CdA on a road bike is 0.3.  Force is
CdA x p x v^2 where p is air density (1.2kg/m^3).  So F = 0.36 v^2 N.  The
energy required to travel 1m is then 0.36 v^2 J (force x distance).  Setting
this equal to 200 (above) we can find the break-even velocity: 200 = 0.36 v^2
so v = 23 m/s (50 mph).

I may have made a mistake, but that ties in with my intuition, so I'll go with
that for now.

Andrew

### [COVID] Coronavirus And Cycling

From: andrew cooke <andrew@...>

Date: Tue, 12 Jan 2021 17:52:10 -0300

There's a long and infamous tradition of physicists / engineers using simple
mathematical models to say something about complex systems (biology,
economics) and getting things horribly wrong.

I'm hoping to avoid that here by not drawing any strong conclusions.  Instead
I just want to throw some light on possible factors that people should take
into account when trying to decide what to do.

OK, so the question people keep asking in various places is: with Coronavirus
being a risk, is it safe to go cycling in a group?

If we assume that transmission is via someone breathing out virus-laden
particles that you breath in (which is, I think, roughly accepted) then you
can try to assess your risk yourself:

* The more people, the more likely someone is to be infected, so the higher

* Breathing out more energetically may increase the number of particles (if it
increases as the volume increases, for example), which is likely a higher
risk.

* Wearing masks hopefully filters out some of the particles, so other people
not wearing masks increases your risk.  The type of mask is likely also
important.

* How diluted the particles become as they move through the air is also going
to be important.  In a closed, poorly ventilated space they will collect.
In the open air they are more likely to be dispersed by the wind.  What
happens when riding in a group?  Is the air 'captured' by the flow through
the group or does it get dispersed?  Presumably a more coherent flow
increases risk.

* Increased spacing between people gives more chance for the particles to be
dispersed.

* Wearing a mask is also important when breathing in, so you not wearing a
mask increases your risk.  And again the type of mask is likely important.

* Breathing in more deeply - as during exercise - may draw more of the virus
more deeply into the lungs, increasing your risk.

Putting all this together, individuals riding separately, well separated,
wearing good quality masks, and not exerting too hard are likely pretty safe.
Change any of those variables and you increase risk.  So, at the other
extreme, many people in a compact group, without masks, working hard, hoping
to drag a "bubble" of air along for efficiency, are going to be more at risk.

Again, I am not a virologoist or an infectologist.  The above is just what
seems like common sense to me.

One example of how I may be incorrect is that, in the argument above, it
doesn't seem to matter who wears the mask (if only one person wears it, and it
filters out 90% of particles, it filters out 90% of particles either when
breathing out, or when breathing in).  This contradicts what I have read
elsewhere, which emphasises that masks are more important on "other people"
and less important for self-protection.

Andrew

### [Programming] Docker on OpenSuse

From: andrew cooke <andrew@...>

Date: Tue, 29 Dec 2020 13:07:46 -0300

* Install docker package(s)

* Start sservice

systemctl enable docker
systemctl start docker

* Add current user to docker group (check it ecxists in /etc/group)

usermod -aG docker username

* Check that things are OK

docker ps

Andrew

### [Bike] Gearing For Real Cyclists

From: andrew cooke <andrew@...>

Date: Sun, 20 Dec 2020 16:58:17 -0300

On the Internet, when discussing lower gearing for a bike, you can expect
someone to comment "just learn to climb".  Which drives me crazy, because the
problem is not the cyclist, but the gears.

Gearing on most bikes sold to new cyclists is not that different to the
gearing that professional cyclists use.  Yet professional cyclists are hugely
more powerful than new cyclists (or even experienced amateurs).  A reasonably
fit, new cyclist can expect to produce around 100W sustained power.  A pro can
maintain 4x that, or more.

The physics of cycling uphill - at least at speeds slow enough for air
resistance to be negligible - is simple.  It's so simple that it's basically
linear - you can adjust things by scaling relative numbers.  In other words,
if a newbie cyclist has 1/4 the power of a pro they should have gears that are
4x as easy.  This is not the case.

To illustrate this I've written a small program that calculates the gearing
you would need (expressed as front x back number of teeth on the gears) for

The power levels cover the range from newbie (100W) to pro (400W+), the
gradients from "normal" hills (6%) to the steepest the Alps can offer (14%),
and the cadences reflect climbing standing (30rpm), grinding slowly sitting
(60rpm), and spinning (90rpm) while sitting.

Note that I have assumed a rider of about my weight (65kg, 143lb).  If you
(plus bike) weigh twice what I do then you need gears twice as low.  Again,
it's simple scaling.

Full results (and the program) are below.  Here I'll pick a few interesting
numbers:

* A 400W pro can spin (90rpm) up a 12% gradient using 39x27.  This is
typical of the lowest gearing on a professional bike (which makes sense).

* A 100W newbie, to do the same, would need 26x72.  That's a 26 tooth gear
at the front and an 72 tooth rear - so extreme it's not even available on
mountain bikes (a 200W rider would need 26x36, which is a MTB gear).

* A 200W amateur rider, with 34x28 gears (about the lowest most new road
bikes go) can spin (90rpm) up a gradient of around 7%, but can manage over
14% if they learn to climb standing at a low cadence (30rpm).

I think the last point is the source of "learn to climb" - learn to ride
standing at low cadence.  But note that this is only an option if you're
a similar weight to me (and many riders seem to weigh much more).

Finally, remember that these are broad generalisations, based only on work
against gravity (ignoring rolling resistance and wind resistance).  But when
we're dealing in differences of a factor of 4, a rough categorization within
10% or so is fine.

Andrew

Cyclist generating 100W
-----------------------

Climb of 6%
30rpm  26x12 30x14 34x16 39x18
60rpm  26x24 30x28 34x31 39x36
90rpm  26x36 30x41 34x47

Climb of 8%
30rpm  26x16 30x18 34x21 39x24
60rpm  26x32 30x37 34x42 39x48
90rpm  26x48

Climb of 10%
30rpm  26x20 30x23 34x26 39x30
60rpm  26x40 30x46
90rpm  26x60

Climb of 12%
30rpm  26x24 30x28 34x31 39x36
60rpm  26x48
90rpm  26x72

Climb of 14%
30rpm  26x28 30x32 34x36 39x42
60rpm  26x56
90rpm  26x84

Cyclist generating 200W
-----------------------

Climb of 6%
60rpm  26x12 30x14 34x16 39x18
90rpm  26x18 30x21 34x23 39x27

Climb of 8%
30rpm  34x10 39x12
60rpm  26x16 30x18 34x21 39x24
90rpm  26x24 30x28 34x31 39x36

Climb of 10%
30rpm  26x10 30x11 34x13 39x15
60rpm  26x20 30x23 34x26 39x30
90rpm  26x30 30x34 34x39 39x45

Climb of 12%
30rpm  26x12 30x14 34x16 39x18
60rpm  26x24 30x28 34x31 39x36
90rpm  26x36 30x41 34x47

Climb of 14%
30rpm  26x14 30x16 34x18 39x21
60rpm  26x28 30x32 34x36 39x42
90rpm  26x42 30x48

Cyclist generating 300W
-----------------------

Climb of 6%
Note: speed > 20kmh, air resistance significant
60rpm  34x10 39x12
90rpm  26x12 30x14 34x16 39x18

Climb of 8%
60rpm  26x11 30x12 34x14 39x16
90rpm  26x16 30x18 34x21 39x24

Climb of 10%
30rpm  39x10
60rpm  26x13 30x15 34x17 39x20
90rpm  26x20 30x23 34x26 39x30

Climb of 12%
30rpm  34x10 39x12
60rpm  26x16 30x18 34x21 39x24
90rpm  26x24 30x28 34x31 39x36

Climb of 14%
30rpm  30x11 34x12 39x14
60rpm  26x19 30x21 34x24 39x28
90rpm  26x28 30x32 34x36 39x42

Cyclist generating 400W
-----------------------

Climb of 6%
Note: speed > 20kmh, air resistance significant
90rpm  30x10 34x12 39x13

Climb of 8%
Note: speed > 20kmh, air resistance significant
60rpm  34x10 39x12
90rpm  26x12 30x14 34x16 39x18

Climb of 10%
Note: speed > 20kmh, air resistance significant
60rpm  26x10 30x11 34x13 39x15
90rpm  26x15 30x17 34x20 39x22

Climb of 12%
60rpm  26x12 30x14 34x16 39x18
90rpm  26x18 30x21 34x23 39x27

Climb of 14%
30rpm  39x10
60rpm  26x14 30x16 34x18 39x21
90rpm  26x21 30x24 34x27 39x31

Python 3 code:

circumference = 2.14  # m (measured from rolling road bike)
g = 9.8  # m/s2 (gravitational acceleration)
mass = 8 + 65  # kg (bike + me)
front_gears = [26, 30, 34, 39]  # teeth
gradients = [6, 8, 10, 12, 14]  # %
powers = [100, 200, 300, 400]  # W
cadences = [30, 60, 90]  # standing, grinding, spinning

for power in powers:
print(f'\nCyclist generating {power}W')
print('-----------------------\n')
vertical_speed = power / (mass * g)  # m/s
print(f'  Climb of {gradient}%')
horizontal_speed = vertical_speed * 100 / gradient  # m/s
if horizontal_speed * 3.6 > 20:
print('    Note: speed > 20kmh, air resistance significant')
wheel_rpm = 60 * horizontal_speed / circumference
subtitle = False
for front_gear in front_gears:
gear_ratio = cadence / wheel_rpm
rear_gear = int(front_gear * gear_ratio + 0.5)  # round
if rear_gear > 9 and (rear_gear <= 50 or not subtitle):
if not subtitle:
print(f'    {cadence}rpm  ', end='')
subtitle = True
print(f'{front_gear}x{rear_gear} ', end='')
if subtitle:
print()
print()

### [Programming] React plotting - visx

From: andrew cooke <andrew@...>

Date: Thu, 10 Dec 2020 17:25:49 -0300

Thought I'd share experience / a recommendation for a library for plotting
with React.

Unfortunately the React wrapper for Sencha / Ext (which is what Mihaela demoed
last Thursday) is not available for free (AFAICT).

So if you're working on the cheap you need to look at options like Recharts,
react-vis, or nivo.  I tried those and Recharts seemed best, but they were all
'opinionated' high-level libraries aimed more at business infographics than
displaying 'scientific' information (IMHO).  And because they are high level
it's difficult to adjust them if your aims are elsewhere.  For example, with
Recharts it was difficult to get an auto-scaling linear axis with 'nice'
intervals - it has much better support for data in groups.

In the end, then, I ended up with visx, which is a lower-level library.  There
is no API for a 'plot' or 'chart' - you need to piece things together from
lines, axes, etc.  But it works well and appears to be reliable.

One problem with a lower-level library is that you describe everything in
pixel coordinates.  Which can be a problem in a responsive context where the
size is not known when React runs

The solution is to use a React hook that provides the width of a component and
then iterate (so render blank, get called again via the hook, and render with
the known width).  This is not trivial - if the new render changes the width
you get an infinite loop - but can be made to work (in fact, this was a bug in
nivo, which I didn't understand until the same logic was made explicit in
visx).  I used useDimensions from react-recipes.

[Edit:] It turns out that visx has a component (ParentSize) that does exactly
this, so I dropped react-recipes/useDimensions and used that.

Documentation for visx is good, but not perfect.  There's an API guide and a
lot of examples but nothing inbetween - nothing to tell you what to do.  This
means that you need to read the code, which put me off at first, but turned
out to be a good thing.  It's nicely written code and includes useful
patterns.  The API guide seems to (I may have overlooked something or be
confused) assume that you understand what attributes are passed through to SVG
elements.

Anyway, the end result was a declarative, interactive (you can move a slider
to change the display), responsive (using MUI breakpoints) plot.  Happy.

Finally, note that most of these libraries are SVG-based (nivo includes canvas
support but it was buggy for me) so not suitable for huge amounts of data.

https://airbnb.io/visx/docs
https://github.com/craig1123/react-recipes/blob/master/docs/useDimensions.md

https://recharts.org/en-US/
https://uber.github.io/react-vis/
https://nivo.rocks/

Andrew

### [Programming] React Leaflet

From: andrew cooke <andrew@...>

Date: Wed, 18 Nov 2020 17:29:13 -0300

If you're using Leaflet (the map presenatation library) in React, you will
probably use React Leaflet.  There are a few things you should know:

* It was just rewritten as v3.

* The rewrite is functional React (hooks etc) and works just fine.

* But it breaks all the examples of event handling on the web.

* And it breaks all the JSFiddle examples.

* And you probably forgot to add the leaflet CSS to your website.

But once you have it working, it really does work.

Andrew