Thursday, 24 August 2023

Bubble-cars and Other Cars of my Childhood

When I was a small boy, I was fascinated by cars. The story goes that when I was about 5, I wandered off and got lost. Somehow I ended up at the local police station, in the days of friendly bobbies on bicycles. When my parents came to collect me, I was sitting outside with a young constable identifying the make and model of every car that drove past.

Children, or boys at least, had way more freedom to roam around than anyone would consider reasonable now. When I was 7 or 8 I would wander all around Harold Hill, the huge council estate in East London where we lived. By the time I was 12 I spent whole days exploring the London red bus network on my own.

The definitive bubble-car - a British Isetta, with
an elegant Rolls or Bentley behind it.
Inside an Isetta - a BMW advert
The Messerschmitt
Messerschmitt Tiger - 4 wheels, 500 cc
The Messerschmitt from Brazil
The Heinkel, with its pointy rear-end
The strange-looking Scootacar
The amazingly ugly Rodley
Morgan Super Sport, with exposed engine
A 1950s Reliant Regal
A Bond Minicar
A (modern) sidecar setup
A 1950s Armstrong-Siddeley Sapphire
The School Board Man's Panhard
Morris 8
1954 Standard 10
A 1950s Standard Vanguard like our Neighbour's

I knew all the cars that lived within a quarter mile radius of my home by heart. On an estate built for working class people, they were still fairly uncommon. Maybe one household in ten had a car, often a pre-war Austin, Morris or Ford, over twenty years old.

For a young man, earning his first wages at the Ford works in Dagenham or at one of the factories on the estate, buying a car was a dream. I'm sure it made getting dates with girls a lot easier, for one thing. But they weren't making that much money. A reasonably new full-size car was way beyond their means.

This was the era of the "bubble-car", tiny cars developed after the war for the growing market of people who wanted a car but still had limited means. A lightly-used example was perfect for the young man looking to increase his mobility, not to say pulling power.

The best known of all them is the Isetta. It indeed looked exactly like a bubble. It had no apparent doors, until you realised that the whole front of the car opened sideways, taking the steering wheel with it via a complicated linkage. A 250 cc engine could propel it to the vertiginous speed of 47 mph, though you would need plenty of room - it took 30 seconds just to get to 30 mph. There was room for two people, sitting huddled together on a bench seat. Safety was non-existent - the humans were first in line to take any impact, and back then seat-belts in cars were unheard of. The little engine was at the back under the parcel shelf. That was the only place for luggage, though there was a rack available to which a small suitcase could be strapped on the back.

The Isetta was originally Italian, built by Iso. They decided to move on to high-powered sports cars, and sold the design to BMW. Compared to the BMWs of today, this seems strange. They were also built under license in Britain, France and Brazil.

When I worked at Cisco in California, in the early 2000s, there was a vintage car club which every now and then would organise an exhibition. It was one of life's little ironies that the Isetta that showed up there belonged to the Director of Corporate Travel.

The other well known bubble-car was the Messerschmitt, built by the famous aircraft company as a diversification after the war. This was a completely different concept. It was long, thin and streamlined, rather than bubble-shaped. Entrance was via a hinged canopy, just like a fighter plane. Its two occupants sat in line, again like in a plane. Steering was through a yoke - like half a  steering wheel. Once again the engine was at the back. It had an interesting way of going backwards. A single-cylinder two-stroke engine can turn in either direction, with just an adjustment to the ignition timing. This car had no reverse gear - if you wanted to go backwards you threw a switch and started the engine in the opposite direction.

The Messerschmitt is still a bit of an icon. They now sell for crazy prices - $30,000 or so for a normal one, and over $100,000 for the rarer Tiger model with four wheels and a higher-powered engine. They were quite common in films in the 1960s, and occasionally later. Probably the most famous appearance is in the Terry Gilliam film-noir, Brazil.

Messerschmitt wasn't the only aircraft company to try its hand at cars. Much less common and well-known is the Heinkel. This was very similar to the Isetta, with a sideways-opening door at the front. It was a bit less bubble-shaped, with a pointy rear-end, very recognisable to my 8-year-old bubble-car connoisseur self. There were several Isettas and Messerschmidts in the area I roamed, but only one Heinkel.

If the cars' owners happened to be around when I was passing by, they were happy enough to chat to me, especially when they realised I knew what I was talking about. That was how I managed to get a ride in something even more unusual, the Scootacar. While the others were Italian or German, this one was all British, built by the Hunslet company which had been making steam railway engines for over 50 years. Supposedly, it was designed the request of the boss's wife, who wanted something easier to park than her Jaguar.

Whereas the other bubble-cars were quite cute visually, the Scootacar was just plain weird. It was much taller, earning it the name "telephone box" at the time. Access was through a side door like a normal car. The shape supposedly came from the designer sitting on the Villiers engine it used, while an assistant drew his outline on the wall.

It would seat two people, as long as they knew each other very well. They sat pressed together astride a longitudinal bench just like a motorbike. The driver at least had the handlebar steering to hang on to, while the passenger had only the driver. About 1000 were made, so I was lucky to see one. Even this oddity has become a collectors' item, worth about £5000 now - 20 times the original cost.

The Scootacar was at least an improvement on the designer's only previous effort at a car, the Rodley. That must have been the ugliest car ever built, all 65 of them. Many were destroyed in spontaneous fires (maybe the inspiration for the Tesla), and supposedly only one still exists. Not surprisingly, I never saw one.

There was one car in the area that was very different. One of the lads had an original vee-twin three-wheel Morgan. Now, an original 1930s model will fetch £50,000 or more. He probably picked it up for almost nothing, an ancient old-fashioned thing that nobody wanted. The thing that struck me was the engine. It was completely exposed at the front of the car, including even the valve gear on top of the cylinders. As the engine idled you could watch the valves popping open and shut on their springs. I wonder how well it dealt with bad weather.

Distinct from the bubble-cars were the two fairly common three-wheelers, looking more like small conventional cars. The Reliant was made famous by the TV programme Only Fools and Horses, but in the 50s they were acceptable family transportation, with a proper 750 cc four-cylinder engine. I saw quite a few around but they were more a car for the settled family man than for a young lad. The latter preferred the Bond Minicar, a sort of motorbike/car hybrid. It was the general shape of a car, but the mechanics were pure motorbike. The single-cylinder engine was just behind the chain-driven front wheel. To start it, you opened the bonnet (hood), and pulled a chain, like kick-starting a bike.

All of these bubble-car and minicar firms were gone by the early 1960s. The introduction of the famous Mini in 1959 didn't help them. Suddenly you could buy a serious car for not much more. My friend bought  one, used of course, when we were teenagers.

As a means of transportation for the working man, motorbikes were much more common than cars. There were all the classic British makes: BSA, Triumph, Norton and others that thrived before Japanese manufacturers had the eccentric idea of building bikes that didn't leak oil continuously and need constant repairs and maintenance. For men with families, there was the sidecar, a one-wheeled pram-like vehicle that bolted onto the side of the bike to accommodate his wife and one child. I remember being taken for a short ride in one of these. Even for a small child it was overwhelmingly claustrophobic.

Not all the cars I met were bubble-cars. My parents were heavily involved in the local Pensioners' Pals, a sort of support group for the old women (hardly any men) who lived in special accommodation on Harold Hill. This brought them into contact with various local officials, doctors and so on. The Mayor of Romford  was driven around in a car from a long-vanished marque, an Armstrong-Siddeley. These were the perfect cars for mayors and minor captains of industry, less ostentatious than a Rolls or Bentley but still very much a sign of being above the hoi polloi. On one memorable occasion, I even got a short ride in its luxurious padded leather interior.

In the 1950s, practically every car on the road was made in Britain and carried a British name - even if it was foreign-owned, like Ford or Vauxhaul. Foreign cars were extremely rare. While my mother was visiting one of her adopted old-age pensioners, I was wandering around and saw an immaculate dark-blue French Panhard parked on the street - recognised only from my Motor Show catalogues, of which more later. I could hardly resist taking a close look at it. It was a school day, but I'd been off with a bad cold. Suddenly I was interrupted by its owner.

"Why aren't you at school?" he demanded with authority. It turned out that he was the "School Board Man"- truant officer in American. It was his job to go around looking for children who ought to be in school, but weren't. I took him to my mother, and things were settled. But it was an awkward moment.

My aunt and uncle bought their first car when I was 7 or 8. It was a pre-war Morris 8, with the very fitting registration OLD 8. Before 1939 it hadn't occurred to people that you should be able to open the boot (trunk) to put stuff in and out. Access was by folding the rear seat. Like all small pre-war cars, it was black.

Later they upgraded to a Standard 10. This was a post-war design, so curved rather than angular, and blue-gray rather than black. It even had an opening boot - although the cheaper variant, the Standard 8, didn't, and you still had to manhandle luggage via the back seat.

In one of life's odd coincidences, my uncle was rear-ended while driving his Standard across Lambeth Bridge by his old Morris 8.

Standard used to be a major, respected British marque of cars, up until the 1960s when they "merged" with Triumph, and disappeared. Then later Standard-Triumph was absorbed by Rover, and then all went down the plughole into British Leyland. One of our neighbours had a Standard Vanguard which was, well, the vanguard of the Standard range. It was the elegantly curvaceous early-50s version, rather than the more angular design which came later. I remember him boasting to my Dad that unlike lesser cars his came with various usually-optional features including a heater.

Yes really, cars in the 1950s often didn't have heaters. When my Dad started driving vans for work, none of the earlier ones had heaters. One of my prized annual acquisitions was the Daily Express Motor Show catalogue. I only went to the show once, but my Dad bought me the catalogue every year. Apart from the cars themselves, four to a page, there were large sections of adverts for accessories. After-market heaters were common there.

My one visit to the London Motor Show at Earls Court in west London was when I was about 10. It influenced my car-buying habits for the rest of my life. I was looking at a Lancia, before they became famous for rusting away practically before you'd driven off the forecourt. I got shooed away because - obviously, as a ten-year old - I wasn't about to buy one. I vowed then never to buy one, and I never have. There was a time, in the 1970s, when Lancias were quite chic, and I might have bought one. One of my bosses had a Lancia Beta, a kind of sports hatchback. It looked very smart, although it did indeed rust away fairly quickly.

Standard wasn't the only marque that has long since disappeared. Wolseley and Riley were the luxury end of the BMC (Austin and Morris) range, with Riley supposedly being sportier. By 1960 they were just badge-engineered version of their Austin and Morris cousins. If you watch 1950s British films, the police invariably drive smart-looking Wolseleys or if they're lucky, the faster Riley Pathfinder, jangling their police bell as they drive. The whole Rootes family - Hillman, Humber, Singer, Sunbeam - were common in the 50s and 60s but completely forgotten now. Rover was the car of choice for country doctors and solicitors (lawyers). The MG name - the sporty end of BMC - still exists, but is now a nondescript Chinese car.

Returning to bubble-cars, I was amazed to discover there is a Microcar Museum, in the outer suburbs of Atlanta in Madison, Georgia. Maybe one day I'll visit and relive all my childhood memories. Maybe.

Sunday, 20 August 2023

Flight to Croatia - My First International Flight in Europe

My story of moving to France and acquiring all the necessary licenses to fly legally here deserves its own article. Suffice it to say that by August 2023, I had everything legally required to fly IFR or VFR, in a US or French registered aircraft - in fact any EASA (mainland Europe) registration.

I've written elsewhere about the loss of "Sierra", my much beloved Cessna TR182, in a landing accident. I didn't plan to replace her, but in the end I bought a very nicely equipped 2003 Socata TB20GT. Whether this was a good idea, or a moment of complete insanity, is still a matter of judgement. My new plane is called "Tango" - not only is this the obvious follow-on to Sierra, but it is also the last letter of her US tail number (as Sierra was for her predecessor).

A major reason for choosing Tango was her avionics. They were rebuilt a few years ago to the state of the art: Garmin G500 PFD/MFD, GTN750/650 navigator combination. The autopilot is the original King KFC225 which works well with the newer stuff.

Flying a plane is the same wherever you are. But local practices differ a lot between countries. Every country in Europe has its own way of doing VFR and its own variations on the rules. IFR is a lot more consistent, which is why I went to trouble of getting a French instrument rating.

I've done a few IFR flights in France now, including a couple on my own. I feel fairly comfortable with the way things work, though I still have to concentrate to understand ATC, whether in French or English.

Years before we returned to France, I joined an on-line group called EuroGA. It's a very pleasant and interesting group of people, and it was a real eye-opener for a US pilot to see how things are done in Europe. (Spoiler: it's very rarely better, easier or cheaper). Since I started flying in Europe, it has been a huge source of help and sometimes just moral support.

Every now and then the group does a fly-in somewhere in Europe. Up until now I hadn't participated, sometimes because I wasn't around, but as often because I just didn't feel ready for "flying to go somewhere" in Europe. Until now, I had only flown within France.

Then a few weeks ago there was suggestion of doing a fly-in to Mali Lošinj in northern Croatia. That's about a three-hour flight from Cannes, where I'm based. It seemed like a great opportunity to expand my limits, and also to finally meet some of the people behind EuroGA.

I signed up, figuring that I could always chicken out at the last minute. As the time got closer, the weather looked good. Even for IFR flight, that matters. Being bounced around inside clouds is no fun, especially for passengers, and there's always the risk of icing. This would also be my wife's first ever flight in the new plane, and her comfort mattered as much as mine.

Planning

I started to prepare flight plans. With ForeFlight this is easy - just enter the origin and destination, and tell it to find a route. In the US you can file any route you want. If ATC don't like it, they will give you one they do like as part of your clearance. In Europe, you can't even file a route if it isn't approved by a pan-European government agency called Eurocontrol.

The outbound route was straightforward. The return route was more of a problem. As part of the route you have to file approved departure and arrival procedures. The only arrival it came up with for Cannes was to take a sharp left around Genoa and to fly practically to Corsica, before returning via a U-shaped route to Cannes.

I know from my own limited experience, and from what others tell me, that you rarely get to fly the whole cleared route. You get a bunch of shortcuts, including very often for the arrival and approach part. The western approach to Cannes is typical. It's a twisty, zig-zag route with half a dozen waypoints, each with its own step-down altitude. Each time I've flown it, before I even arrive at its official start, I've been cleared direct to the final approach fix, OBOTA, with a few descent clearances to get me there at the required altitude of 2000 feet.

Still, I didn't want to sign up to fly to Corsica. Apart from the time, distance and fuel, flying over the sea in a single engine plane is best avoided or minimised if possible. I asked around, and looked at what other aircraft do. There's a different procedure, which avoids flying to Corsica. It's universally used by jets arriving from Italy and points east. For me, the problem is a segment with a minimum altitude of FL140 (14,000 thousand feet, more or less). My non-turbo-charged plane would take a while to reach this altitude. It's also the maximum legal altitude, under FAA rules, without an oxygen mask, which I don't have. Also, for the route to be accepted, I would have to file the whole route at FL140, which I definitely didn't want to do.

Then someone showed me a way to file lower, and to request just FL125 over the segment in question. I filed that, it was accepted, and things were looking good.

The route from Cannes to Croatia unavoidably crosses the northern part of the Adriatic. This is about a 60 mile over-water crossing. If the engine stops there, you will certainly get wet. Aircraft aren't designed to float, and once they sink, you're on your own. A life-vest will keep you afloat, but unless you manage to ditch alongside a boat, things are unlikely to end well. The best thing is to carry an inflatable canoe, but these are expensive and heavy. Luckily, my flight instructor volunteered to lend me one. I hope I'll never have to try it out.

Flying

The weather forecast continued to be perfect as the departure day approached. Then on the morning of the flight, there was a NOTAM that the tower at the destination airport was closed. I guess the tower operator didn't show up for work. That meant that not only did the arrival have to be VFR, but also there was nobody to tell me what to do around the airport. I'm still nervous about uncontrolled airports in France, never mind Croatia. Controlled airports are easy, you just do what you're told. But at uncontrolled fields it's up to you to figure out the right runway to use, the right traffic pattern to fly and to keep a very careful eye out for conflicting traffic.

Mali Lošinj airport has a very complicated set of charts for flying VFR, with numerous points that you have to overfly, routes you have to follow, and altitudes you have to obey. I'd prepared for that - I even had a printout of Google Maps with the various points noted on them.

EuroGA had created a Telegram group for the fly-in. We were already at the airport when an early arriver sent a message that the parking was already full, and they were putting planes on the grass. Parking on grass is fine but what if there was nowhere left, or if I couldn't figure out where to park?

Feeling distinctly uneasy, I decided to set off anyway.

IFR departures and arrivals at my airport (Cannes, LFMD) are more complicated in the summer. The airport is "coordinated" which means that in addition to a flight plan and ATC slot, you also need a slot assignment from the airport, through a completely separate process. It's yet another piece of bureaucracy, but the few times I've done it, it has worked fine.

Flight preparation and takeoff went smoothly. I was surprised to get cleared direct to the eastern coast of Italy (Chiaggio, CHI) as soon as were clear of the approaches to Nice, and while we were still talking to French ATC. Over eastern Italy we got cleared to the handover point to Croatia, LABIN. And as soon as we contacted Croatian ATC, they reminded us we would need to cancel IFR because the tower was closed, and cleared us direct to the destination (LDLO).

Nobody seemed to care about all those points and altitudes, though I did aim for one of them anyway. We descended gently from our cruise altitude of FL110 (11,000 feet) to the prescribed 1000 feet. There was nothing in the air when we got to the airport, so I flew the approved French-style uncontrolled arrival, landed and taxied to the ramp. The runway is on about a 2% slope, which doesn't sound much but means the runway looks distinctly uphill as you arrive. The TB20's trailing-link gear means that almost all landings are smooth, but this one wasn't.

Arrival

First sight of Mali Lošinj, at the end of its bay.
Arriving at LDLO's sloping runway.
Crowded airport, with fuel truck.
Mali Lošinj Harbour.
First sight of Susak.
The airport was indeed quite full when we got there, with planes parked all over the place. To my surprise, a yellow-jacketted marshaller appeared and guided us onto the grass, along improvised taxiways and into a parking spot. And then, once I'd shut down, he asked if I wanted fuel, and summoned a fuel truck.

One of the few things I miss from our time in the US is the fuel truck at Palo Alto. Fuel trucks are almost unknown for small planes in France. Usually you taxi up to the pump and do everything yourself, just like with a car. Tango has low wings, so it isn't too difficult. But with Sierra's high wings it was a real nuisance, clambering up a ladder whilst dragging the surprisingly heavy fuel nozzle.

So to have the pleasure of a fuel truck at a tiny uncontrolled field was truly amazing. We filled the tanks to the brim, mainly to be sure of exactly how much we had.

Two other planes from the group arrived shortly after us. We hung around until everyone was ready, making the most of the very pleasant, modern, air-conditioned terminal building. Everybody at the airport was pleasant and helpful.

There was an airport taxi - a big van affair with 7 seats - waiting for us. We had to hurry the other people along because there is a bridge along the route to town, that closes at 6pm - and by now it was 5.40.

Mali Lošinj

The arrival in the town was delightful. The old part of the town is wrapped in a U-shape around a large port, full of small boats of various kinds with some larger ferries. The island is a perfect natural harbour, screened from the Adriatic by various other islands and channels. It was once one of the largest ports in Croatia. Legend has it that nearly every man in the town was a sailor.

We relaxed, showered, and had a beer at one of the many bars and restaurants that line the waterfront. The charm of the town comes from its inaccessibility. If you don't arrive by plane or boat, the only other way is by ferry. There are car ferries to the island, but it involves a lot of driving as well. There are no big, modern hotels, just a few more traditional places and many vacation rentals.

Our hotel, the Mare Mare, had just a dozen or so rooms. It was nicely decorated, comfortable and everybody there was charming and helpful. From our window we looked out over the boats in the harbour. As we discovered next morning, many people arrive on foot using the ferries from the mainland, carrying beach chairs, tables, food, small children, and everything else needed for a day at the seaside.

At dinner we met the rest of the EuroGA group, in particular Peter who started it and still gives it a lot of time and energy. It was a very simple dinner, just whole fish cooked in different ways. It was absolutely delicious, accompanied by a tasty local white wine made from the Malvazija grape.

To The Sea

Our idea the next day was to visit one of the other islands, by boat. But as usual we got up late and by the time we were ready, all the regular tourist promène couillons had left. We stood in line to take a regular ferry, but it wasn't looking very promising - I suppose they get booked in advance. Luckily for us a German couple standing behind us overheard our conversation. They'd spoken with a private boat operator, but he wanted four people. We quickly agreed to make up the foursome, and soon we were on our way out of the harbour towards the island of Susak.

The island has a long and complicated history. Like much of the Dalmatian coast, it has belonged over time to Rome, Venice, Austria, Italy and Yugoslavia. In the 1950s practically the entire population debunked to the US - Hoboken, New Jersey, which to someone who grew up on an idyllic Mediterranean island must have been an uncomfortable change.

Since then some have returned and apparently the language spoken is now a mix of English, Croatian and the local dialect which nobody else has ever understood. The permanent population is now about 100, down from 1600 in the 1940s. It's accessible only by boat, with a year-round ferry service to the main island.

We wandered round, dipped our feet in the sea, then returned to the boat for an indifferent lunch. But it was nice of them to serve it.

The boat passes through a narrow and very shallow passage between the main island and a smaller one. The much bigger regular ferries take a longer way round through a much deeper channel.

The hotel had already told us that they could lend us bicycles, so we planned to use them to visit a beach. These are on the opposite side of the island, on the relatively open sea facing the Croatian mainland. Unfortunately the bikes were in really terrible shape. Nobody expects the gearshift to work on rental bikes, but these really didn't work. We just about persuaded them to cross the slight rise towards the beach, but there our courage gave out. The nearest "beach" to the town is really a series of concrete platforms on the rocks, which suited us very well. We both went for a swim, then  a walk along the coast path before tackling the bikes again for the short ride back to our hotel.

Dinner that night was right next to our hotel. It was, again, three large whole fish, each prepared differently. It was absolutely amazing. The best of all was a sea-bass, baked in salt and then dismembered at the table. I think it's the best fish I've ever eaten in my life, succulent, juicy, tasty and just all-round delicious.

Return

Takeoff, with Croatian Alps on Mainland
I'd filed a flight plan for a 12h05, departure, which should line up reasonably well with our 15h00 arrival slot at Cannes. We had a relaxed breakfast, went for a little walk along the quay, then boarded our airport shuttle back to LDLO.

There wasn't much to do when we got there. The plane was already full of fuel, so all it needed was to remove and fold the cover (always quite an exercise), do a normal pre-flight, and figure out how to get from our parking spot to the taxiway. At the appropriate time we climbed in and requested start-up clearance, which we received. Then one of the marshallers ran up to the plane making hand signals which evidently meant "whatever you were going to do, don't". We had to shunt the plane around by hand until we were aligned with a different impromptu taxiway, then taxi out very gingerly since one wing passed over some airport equipment and the other close to another plane.

While we were waiting to leave we got to watch a Croatian medical evacuation. A huge Soviet-era Mi8 helicopter landed and hover-taxied to a corner of the ramp, making an incredible racket. An ambulance joined it, the patient was moved, and the Mi8 hover-taxied back to the runway and departed. The whole procedure took less than 10 minutes. It was very impressive.

Today the tower was staffed, meaning that we could depart IFR. The wind meant we took off on runway 02, meaning up-hill. The hill continues after the runway and we seemed pretty close to the vegetation, but everything was fine. Our filed route took us north initially, before turning left towards Italy. But as soon as we contacted Pula Approach they gave us direct LABIN, the handover point to Italy. And so it went, the dozen or individual waypoints comprising the filed airways all ignored as we flew with just three direct clearances to Genoa.

My old Garmin GNS530 in Sierra didn't know about airways. I would have had to enter all the intermediate waypoints laboriously by hand, twiddling knobs to enter each letter. The GTN750 in Tango is a dream, once you get used to it. You just tap the last waypoint on the flight plan so far, tap "add airway", it gives you a choice of airway and then the exit point. From there you repeat this until you reach the end of the en-route part, then add the destination airport and any associated procedures.

Approaching Cannes, Les Iles de Lerins,
packed with small boats
Giant cruise ship at Cannes

I was still concerned about having to climb to FL140 for the sector to BORDI. I needn't have worried. Overhead Genoa we were cleared to OZMIC, the "frontier point" between France and Italy. That feeds neatly into the Cannes approach at INLOV. There was someone in front of us so we got vectored a bit, but it was very straightforward.

As we flew towards the airport we had a magnificent view of Les Iles de Lerins, as always with hundreds of boats packed into the channel between them. Off the shore was a giant cruise ship. We couldn't help comparing the packed, claustrophobic environment of the ship very unfavourably with nearly-empty Mali Lošinj.

Next came the very odd approach to runway 17 at Cannes. It consists of flying north towards runway 35 (which is to say in the opposite direction) then breaking off into a giant circle-to-land, officially called a Visual Prescribed Track (VPT). This could be easy, but thanks to French bureaucracy and the airport's neighbours, it isn't. You have to fly precisely over two points, but the DGAC (French regulator) will not disclose their precise location. So you have to look out of the window and try to identify them visually, while flying a descending turn. And if you get it even slightly wrong, you overfly the airport's nimby zones. You can guess how I know.

It turns out that there is an organization of elderly nimbies in the surrounding villages that spends all day watching airport tracks and filing complaints about them, whether or not they infringe the nimby zone. Yesterday they filed 127 complaints, probably at least one for every aircraft flying the VPT.

We were on the ground within less than five minutes of our arrival slot, which is pure good luck. Tower told me of my accidental nimby overflight as I left the runway, though they were very nice about it.

And that was our weekend in Croatia, 6.3 hours of flight time and the discovery of an idyllic location for future weekends. All thanks to EuroGA and to Peter.

Wednesday, 9 August 2023

Fun with AVX and Wordle

A First Attempt - in Kotlin

When the Wordle game first appeared a couple of years ago, I wrote a helper program to do various things, in particular to suggest the best word to try given progress so far in the game. More later on how that works. I wrote the program in the fairly obscure language Kotlin, partly because I like it but also for performance. The "best next word" function potentially requires comparing every word in the dictionary with every other, making for millions of comparisons. The obvious language for this kind of job is Python, but it generates very slow interpreted code - around 100 times slower than native machine code. The absence of strong compile-time typing also makes it painful to work with, for anything longer than a page or so of code.

Kotlin addresses both of these problems. It has strong typing, nicely done so it mostly doesn't get in the way. And it compiles to JVM code, which supposedly is within a factor of 10 of native machine code. It also has an awful, incomprehensible build system, but that didn't matter for this problem since I wasn't planning on generating a stand-alone program. Working within the IDE was fine.

Another advantage of Kotlin is that it very easily supports multi-threaded operation, taking full advantage of a multi-core CPU. The problem lends itself very easily to a highly concurrent implementation. As is well-known, Python's thread support only allows a single thread to run at a time (the dreaded "Global Interpreter Lock").

My Kotlin program worked, but its performance left a lot to be desired. Running the "best word" algorithm at the start of the game, when the number of possibilities is largest, takes about 40 seconds, and that is running on an 18 core system. The htop command shows that every one of the 18 cores is running at 100% for as long as the command takes to complete.

At that point the program had served its purpose. It was an interesting exercise in writing functional style code in Kotlin. I'd figured out the fairly complex algorithms for comparing and matching words. I had no real intention of using it to play Wordle anyway.

AVX

AVX, and more specifically AVX512, is the latest stage in the development of so-called vector processing for the Intel/AMD X86 family. (That's no longer quite true, because Intel recently announced further extensions called AVX10 - but it will be a while before you can buy a CPU that implements it).

Vector-processing instructions, or SIMD (Single Instruction Multiple Dispatch), have been around for a couple of decades now, originally called SSE. They have gone through several evolutions. AVX512 provides 512-bit registers which can hold 16 32-bit floating point or integer values. A single instruction can do a simultaneous multiply/add with 16 values. With careful coding to keep the pipeline full, one of these can be executed every clock cycle, so about 50 billion multiply/adds per second. And that's per core - a typical server CPU now has 24 cores, so around 1 trillion operations per second.

AVX was conceived for arithmetic operations, especially vector/matrix/tensor multiplication. You don't need to know anything about AVX to use it for this - there are excellent libraries, and even without them the compiler can often figure out how to optimise "normal" code to take advantage of AVX.

Over time, there have been various additions and tweaks to the available instructions to support a wider range of applications. One of the most impressive is simdjson, which uses AVX to accelerate parsing JSON text by a factor of 5-10. It is far from obvious how to do this, even when you have read the paper describing it!

In recent years, matrix and tensor processing has become much more important because of Artifical Intelligence. Neural networks, at the heart of AI, involve huge amounts of matrix arithmetic. For a while the preferred approach was to use GPUs, which are essentially matrix multiplication engines. But with AVX, you can get comparable performance from a CPU, and without the overhead of moving vast quantities of data to and from the GPU.

Unless you code in assembler, you don't get to write individual AVX instructions. Rather you use intrinsics, built-in functions defined by Intel but implemented on GCC and other compilers. Some of these map directly to specific AVX instructions, while others are rearranged and optimised. As a C++ (or whatever) programmer, you don't need to know. But if you're interested, you can always look at the assembler output (-S option) from the compiler, or with the debugger.

The 128, 256 and 512 bit AVX registers don't correspond to any normal data type, so special types are defined for them like __m256 (256 bits representing 8 32-bit floats) and __m512i (representing 16 32 bit integers). The compiler figures out how to use corresponding registers or to map them to memory.

It was AI that led me to take a close look at AVX, for professional reasons. And that got me wondering whether there was a way to speed up the key algorithms for Wordle.

Wordle Algorithms

There are two key algorithms in any program designed to work with Wordle. I'll use the terms target word for the unknown word you're trying to find, and test word for the word you type, looking to see how closely it matches the target.

The first algorithm compares a test word with the target word, finding which letters are exact matches, the right letter in the right place (colored green in the Wordle game) and which are partial matches, the right letter but in the wrong place (colored orange in the game). This is harder than it looks, mainly because of words containing repeated letters. For example if you compare "every" with the target "trade", one "e" is orange and the other black: every. If you compare "poppy" with "plump", you get one "p" of each color: poppy.

The second algorithm - not needed by the game itself but required by any kind of helper app - is to take a word and a match pattern (which letters are green or orange), and determine whether it could apply to a given target word. Once again this is made harder by repeated letters. The result every matches words containing a single "e", but not words with two or more. Whereas every matches words with two "e"s or more, but not those with only one.

Another algorithm is needed to select the best word to refine the set of possible words. The goal is to divide the remaining words into the largest number of distinct subsets, of more or less equal size. For example, suppose that there are 16 remaining words. The ideal test word would create 16 sets each of one word, so there is only one possible choice. That's unlikely, but four sets of four words each is much better than three sets of one word and one containing the remaining 13.

There are no doubt many ways to do this, but the one I chose was to use the concept of entropy taken from information theory. The entropy of a collection of values is given by the formula:

where pi is the probability associated with this value, such that the sum of all the pi values is always 1.

In our case, we count the number of words associated with each distinct value of the match result. Then,

where ni is just the number of words associated with a given result.

So to find the best word, we take each word in the dictionary as a test word, and calculate the entropy when it is applied to the remaining set of words after the preceding test words. The one with the highest entropy is the best.

Combining AVX with the Algorithms

My starting point for using AVX was to represent a word as a list of bitmaps, one for each letter. A letter map is a 26-bit map (represented in a 32-bit integer) with one bit for each letter. When representing a specific word, it's a one-hot map, i.e. only a single bit is set. But it can also represent a set of possible letters.

A word map is a list of letter maps, one for each of the five letters in a Wordle world. Conveniently, this fits into a single AVX 256-bit register, meaning operations can be performed on a whole word in a single instruction.

It's obvious that the exactly-matching letters between two words can be found simply by taking the logical-and of their respective word maps. There will be a bit set in each letter position where there is an exact match, and nowhere else.

This needs to be turned into a 5-bit bitmap. Fortunately AVX512 contains the perfect instruction for this. Each 32-bit integer can be independently compared with a value, setting the corresponding bit in an 8-bit mask if the condition is satisfied. So in our case, we set up a register with zero in every position, and compare for greater. In two AVX instructions - the logical-and followed by the compare-to-mask - we can generate the bitmap for the exact match.

Sometimes we need to count the number of matches. There is another (non-AVX) intrinsic for doing this, __builtin_popcnt which counts the number of set bits in a word. So to count the number of matching letters takes just three instructions:

  • logical-and of the two word maps
  • compare-to-mask to generate a mask word with a 1 for every matching position
  • __builtin_popcnt to count the 1-bits

Finding the partial matches would be easy - if there were never any repeated letters. We create a word map  with every letter in the target at every position, except where an exact match has already been found. Then we do the logical-and and compare, as above. Every letter that us a partial match will be a 1.

This fails for repeated letters though, whether in the target or the test word (though differently). To fix this, we have to keep separate letter and word maps for twice- and thrice-repeated letters. The exact algorithm is too complex to explain here (but can be seen in the code).

This done, the complete match() function amounts to about 20 instructions, mostly AVX.

The conforms() Algorithm for AVX

Like match(), conforms() would be very simple if no words contained repeated letters. It can be done as follows:

  1. Ensure that the target contains no letters that failed to match in the test word. This is a single logical-and between the respective letter masks.
  2. Ensure that the target contains all letters that have matched (partial or exact) in the test word. This is also a logical-and of the letter masks.
  3. Ensure that any exact matches are indeed matched. This is a logical-and between the word masks.
  4. Ensure there are no unintended exact matches betwen the two words, again a logical-and followed by a test for zero.

However this is complicated by repeated letters, which may result in both false positives and false negatives. Each repeated letter has to be dealt with separately - there can be at most two of them in any valid English five-letter word. For each letter, we create a map of where to look for it (excluding exact matches) and a count for how many we expect to find. There are two cases of that:

  1. The match result contains no failed matches for the letter, as described above. In that case it's OK if the number of matching letters is greater than the count.
  2. The match result does contain failed matches. In that case the number of matching letters must be exactly the same.

Normally, a match result is tested against a lot of other words - the set of all words that have matched so far. At the beginning of the game this is all words in the vocabulary. So it makes sense to generate an optimised match_target object that contains all of the necessary letter maps and word maps as well as information for repeated letters. Then every test is just a bunch of logical-ands and related instructions, mostly AVX.

Entropy for AVX

The entropy calculation is fairly complicated, and is performed for every possible target word during the "find best word" operation. So it is worth optimising.

The naive algorithm described above requires pre-computing the total of all values, then performing a division for every value. These can be avoided with a bit of algebra, resulting in the following formula:

This looks complicated, but it's very easy to compute efficiently. A single loop computes the first term, and the sum of all values (Σn). A single calculation at the end makes the adjustments.

There's a very nice header-only library called avx_mathfun which provides the usual math functions implemented with AVX, so this operation can be done 16 at a time.

There is one complication: taking the log of zero returns the value nan (not a number). But it's easy to replace all zeroes with 1 before taking the log. The resulting product is zero, as it should be.

The algorithm works on 16 values at a time, in parallel. At the end, the 16 partial sums are all in the same 512-bit register, and must be added together. Perhaps surprisingly, AVX doesn't provide a "horizontal add" instruction, not even as an intrinsic. It has to be done by first adding pairs taken from 16 to produce 8 partial sums, then again to produce 4, then 2, and finally a single value.

Results and Performance

Once I'd figured out the key algorithms, it was quick enough to implement them and to verify their correctness. Rather more time was spent putting the whole program together with a command line interface and all the rest. I included code for measuring the performance of the individual key algorithms.

The result was a pleasant surprise. The original Kotlin code took about 700 core-seconds to find the best next word, at the start of the game. My new AVX code does it in 770 mS, or nearly 1000 times faster. Most of this time is spent in match(), which takes about 23 nS to produce the match result for two words, i.e. about 43 million per second. That is just running on a single core - with this performance it isn't worth the complication of running on multiple cores.

I'm running on a 3 Ghz, 18-core Intel i9, though this program only uses one core.

The AVX entropy() function takes about 570 nS to compute a single entropy result. This is about five times faster than the naive implementation doing each calculation separately. At first I wondered why this wasn't better - after all it is doing 16 operations in parallel. But the naive implementation can skip zero values, whereas the AVX implementation can't unless a whole 16-value AVX register is all zero, which is unlikely.

This could be improved, by considering only the possible match results. There are actually only three outcomes per letter - exact, partial, or none. So the number of useful match results is 243 (35) rather than 1024. This would improve performance by a factor of 4 or so, but the entropy calculation is less than 1% of the total work, so it isn't worthwhile.

Conclusions

You can do cool stuff with AVX and go a lot faster. That isn't really a surprise. But it isn't easy, once outside the classic mathematical workload.

As a programming discipline, it is very different from traditional one-thing-at-a-time computing. You can't do A, then depending on the outcome do either B or C, at least not in a general way. In that respect it is like programming a quantum computer - not that anyone will be doing much of that for a long while yet. There also, you have to carry all the results in parallel, without asking them to interfere with each other.

If you want to take a look at actual code, it is here. The interesting stuff is in the files wordle_word.cpp and entropy.cpp.

Update, 2024-11-20

Sutom and AVX512 Performance


There's a French variant of Wordle called Sutom, published in Le Monde. It uses longer words, of varied length up to about 10 letters. The first letter is given as a clue, and guesses must repeat any previously-guessed letters. I thought it would be good to adapt my program to support that, too.

Implementing the slightly changed rules is straightforward. Supporting words longer than 8 means using a 512-bit (16 x 32) representation for the word. That in turn means using the 512-bit AVX instructions instead of the 256 bit ones. There is a performance penalty for these instructions, at least on Intel - not just the instructions themselves but the whole CPU clock gets slowed down, so all instructions are affected.

I reworked the code to hide all the AVX intrinsics behind an avx:: namespace, using function overloading so the caller doesn't need to know whether they're passing an _mm256i or an _mm512i. That makes it very easy to confine the distinction to a single typedef, so it's easy to build a 256 bit or 512 bit variant without modifying any executable code.

In the event though, the performance impact is small. Using 5-letter words, with a 512-bit representation, is about 15% slower for the most peformance intensive functions. This is unnoticeable for a human user.

The VPCONFLICT Instruction


The documentation for AVX instructions and intrinsics is very poor. Individual functions are documented, though details like parameter order often have to deduced by trial and error. But without reading the hundreds of pages one by one, there's no overall summary of the kinds of things you can do. To make it worse, the different word lengths aren't orthogonal. There are 512-bit instructions that don't exist for 256-bit values, and vice versa. Where the same function is available, they are often still subtly different.

So it was quite by chance that I discovered the VPCONFLICT instruction and its associated intrinsics _mmnnn_conflict_epi32(). This compares every 32-bit word with every other, finding identical values. I have no idea what it's for as far as vector arithmetic is concerned, but this corresponds exactly to the problem of finding duplicate letters in a word.

One wrinkle is that it doesn't tell you about each repeat. Rather, it tags each value with how many previous values it conflicts with. So the word abbccc will return {0,0,1,0,1,2}. This is useful, but since my algorithm requires, for example, a mask containing all letters that have been repeated exactly once, it takes some craftiness to use the value returned by the instruction.

The challenge was to write the code as just a linear sequence of AVX instructions, without any branches or looping. Reinstating the first and second occurrences of repeated letters is done by:
  • use the conflict() intrinsic to find conflicts
  • create a _mmask value representing all non-zero values
  • use that to create a single letter mask of repeated letters, with a horizontal OR
  • replicate that to every position
  • compare the original word mask to this
  • anywhere that matches is a repeated letter
I wrote this code just for fun. It's only used when loading the dictionary, which only takes a few milliseconds when the program starts up. Performance is about 25% better than the obvious approach of explicitly comparing the letters with each other and looping over the result. But it was fun.

AVX2 Compatibility


There's a machine I sometimes have to use that only has AVX2, i.e. 256-bit instructions. It would be nice to be able to run the program on that.

The compare_mask instructions are only available in AVX512, even for 256-bit values. So these have to be replaced with explicit calculation of the mask, by looking at each 32-bit word in turn. This is also the case for the mask_blend instructions, that take a bitwise mask and use it to select which value to use from two inputs. So that too has to be emulated.

The conflict intrinsics, described above, are also not available, but I already had the code to do this.

Overall performance for the AVX2-only code is about 25% worse than for AVX512, which doesn't matter in practice. That would be a bit worse if 512-bit instructions were emulated using two 256-bit instructions, but I doubt it would be noticeable to a human user.