Andrew Cooke | Contents | RSS | Previous
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.
Choochoo Training Diary
[USA Politics] In America's Uncivil War Republicans Are The Aggressors
[Programming] Selenium and Python
[Bike] How Fast Before Walking More Efficient Than Cycling?
[COVID] Coronavirus And Cycling
[Programming] Docker on OpenSuse
[Bike] Gearing For Real Cyclists
[Programming] React plotting - visx
AliExpress Independent Sellers
[Politics] Back + US Elections
[Programming,Exercise] Simple Timer Script
[News] 2019: The year revolt went global
[Politics] The world's most-surveilled cities
[Restaurant] Mama Chau's (Chinese, Providencia)
[Politics] Britain's Reichstag Fire moment
[Programming] GCC Sanitizer Flags
[GPU, Programming] Per-Thread Program Counters
My Bike Accident - Looking Back One Year
Applebaum - Twilight of Democracy
You can Masquerade in Firewalld
Update - Garmin Express / Connect
© 2006-2015 Andrew Cooke (site) / post authors (content).
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
Permalink | Comment on this post
For comments, see relevant pages (permalinks).
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
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) self.driver.find_element_by_link_text("register").click() with self.smtpd(): self.driver.find_element_by_id('username').send_keys('andrew') self.driver.find_element_by_id('email').send_keys('andrew@...') self.driver.find_element_by_id('password').send_keys(self.PASSWORD) self.driver.find_element_by_id('retype_password').send_keys(self.PASSWORD) 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.username) self.assertEqual('andrew@...', user.email) self.assertEqual(dt.date.today(), user.email_confirmed_at) self.assertTrue(len(user.password) > 20) self.assertTrue(user.password.startswith('$')) 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
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
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 your risk. * 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
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
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 different power output, gradients, and cadences. 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 for gradient in gradients: 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 for cadence in cadences: 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()
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
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
From: andrew cooke <andrew@...>
Date: Sat, 14 Nov 2020 09:59:36 -0300
Not sure the title makes much sense - these are AliExpress stores that sell their own branded cycling tops (rather than copies of other people's designs). This list isn't exhaustive and may contain errors, but it's a useful starting point for trying to find sellers that are doing more than pushing copies (you might hope these companies are more invested in making something exceptional - perhaps better quality materials, a different style, or better service). There's quite a mix here and no obvious way to group, so ordered alphabetically by name. I haven't bought from most of these and make no guarantees about quality or reliability. Andrew Cheji - https://cheji.aliexpress.com/store/327485 All products are own brand. Designs vary (perhaps aimed at non-western market?). Cusroo - https://id.aliexpress.com/store/2800067 Most products are own brand. Designs vary. Indonesian? Darevie - https://cyclingwear.aliexpress.com/store/3103076 All(?) products are own brand, with some shots using own model. Designs tend to be geometric patterns. Frenesi - https://www.aliexpress.com/store/4388076 Most of the products are copies, but those branded Frenesi appear to be own-designs. Generally simple designs. HIRBGOD - https://hirbgod.aliexpress.com/store All(?) products are own brand. Designs are generally garish novelty items. MTSPS - https://mtsps.aliexpress.com/store/3147007 All products are own brand. Designs vary (like Cheji, maybe aimed at non-western market?). Pimmer - https://id.aliexpress.com/store/5047171 A few products are own brand (beware - many appear own brand but contain other company logos on close inspection). Minimal designs. Possibly experimenting with different fabrics / cuts (see https://www.aliexpress.com/item/33019852324.html for example). Racmmer - https://racmmer-cycling.aliexpress.com/store/1052221 All products are own brand. Designs are, well, mixed. Well known and high volume. A bit more mass-market that Santic or Spexcel (but I Like their pro fit). Rion - https://www.aliexpress.com/store/3511051 All(?) products are own brand, with some shots using own model. Designs are basic. Seems to be fairly well known / large volume (like Santic, Spexcel, Racmmer). Runchita - https://www.aliexpress.com/store/3521027 Most products are copies, but Runchita in the catgeory menu selects own-branded. Designs vary. Santic - https://santickorea.aliexpress.com/store/1985488 All products are own brand. Has an identity outside Aliexpress and tends to be more expensive. Shots with own model. Generally simpler designs. Spexcel - https://www.aliexpress.com/store/2219030 All(?) products are own brand, with some shots using own model. Designs are simple (Rapha influenced?) with horizontal bands. Have bought from here - seem to experiment more with different fabrics and designs. Fairly well known. Swiftofo - https://www.aliexpress.com/store/1074373 All products are own brand. Designs are generally simple, but looks like they may be aiming at a more athletic (triathlon?) market - more athletic cuts and different materials. Many shots using own model.
From: andrew cooke <andrew@...>
Date: Sun, 8 Nov 2020 16:56:34 -0300
Well, this is something of a test post to see if posting here is working again. Also, we are just post-US elections, which were really worrying. There's a lot of euphemisms being thrown around. On 538 people keep talking about how 'this kind of politics' is not going to go away. They mean Fascism. A huge chunk of the country made clear they thought continuing to support an openly fascist regime was a good idea. Call it by it's name. Andrew
From: andrew cooke <andrew@...>
Date: Sun, 29 Mar 2020 17:29:17 -0300
The following may be useful to someone doing exercises at home during quarantine. It will only work on Linux since it uses ALSA for sound. For example time 1 -x 3 -d 5 will give 3 beeps, 1 second apart, 5 seconds after you hit return. If there is a delay the start is indicated by a high-pitched beep. The last interval is ended with a low-pitched beep. Andrew #!/bin/bash function beep() { FREQ=$1 TIME=$2 ( speaker-test -Dsysdefault -t sine -f $FREQ 1> /dev/null 2> /dev/null)& pid=$! ; sleep $TIME ; kill $pid 2>&1 } COUNT=1 DELAY=0 while (( "$#" )); do if [ "$1" == "-h" ]; then echo echo "timer (mainly for timing exercises)" echo echo "usage:" echo echo " $0 TIME [-x N] [-d SECS]" echo echo " -x N repeat N times" echo " -d SECS wait SECS before starting" echo exit 1 elif [ "$1" == "-x" ]; then shift COUNT="$1" elif [ "$1" == "-d" ]; then shift DELAY="$1" else DURATION="$1" fi shift done if [ -z ${DURATION+x} ]; then echo "no duration (see $0 -h)" exit 2 fi echo echo "duration: ${DURATION}s" echo "delay: ${DELAY}s" echo "count: ${COUNT}" echo if [ $DELAY -gt 0 ]; then sleep $DELAY beep 2000 0.5 fi LOOP=0 while [ $LOOP -lt $COUNT ]; do LOOP=$(( $LOOP + 1 )); echo "$LOOP" sleep $DURATION if [ $LOOP -eq $COUNT ]; then beep 500 0.1 else beep 1000 0.1 fi done