• Kohlendioxid auf dem Balkon

    Nicht offensichtlich korrelierte Kurven von CO_2, Windgeschwindigkeit und Temperatur

    CO2-Konzentrationen auf meinem Straßenbalkon, zusammen mit Windgeschwindigkeiten und Temperaturen. Das ist ein SVG, es lohnt sich also durchaus, in einem separaten Browserfenster in den Plot zu zoomen.

    Ich habe neulich eine längere Zeitreihe mit CO2-Konzentrationen auf meinem „vorderen” Balkon genommen. Zur Einordnung: Das Messgerät steht so etwa 10 Meter über und 15 Meter neben einer halbwegs viel befahrenen Straße. Ob das wohl etwas mit den wilden Schwankungen zu tun hat, die in der Kurve oben vor allem um den 9.11. herum zu sehen sind? Muss ich meine Einschätzung von neulich, einzelne Autos seien selbst im mittleren Nahbereich im CO2 kaum nachzuweisen (nun: an der frischen Luft, natürlich), revidieren?

    Verheizt jemand 100000 Tonnen Kohlenstoff am Tag?

    Wer die Kurven von Windgeschwindigkeit[1] und CO2-Konzentration vergleicht, könnte schon glauben wollen, ohne externe Frischluftzufuhr (also bei niedrigen Windgeschwindigkeiten) gehe das CO2 lokal merklich nach oben. Wirklich überzeugen kann mich aber keine Korrelation zwischen den verschiedenen geplotteten Größen.

    Darum gehe ich die Frage zunächst deduktiv an: woher könnten die enormen Schwankungen der CO2-Konzentration wohl kommen? Wir reden hier von einer Spanne zwischen 260 ppm und über 400 ppm, wobei es vorkommen kann, dass ich innerhalb von wenigen Stunden 100 ppm mehr CO2 sehe. Der langfristig ansteigende Trend macht mir übrigens weniger Sorgen: Wenn die Photosyntheserate Richtung Winter dramatisch sinkt, die Emission aber z.B. wegen Heizung eher zunimmt, ist das angesichts der beschränkten globalen Durchmischung der Atmosphäre auf der Erde zu erwarten[2], auch wenn das vielleicht nicht gerade innerhalb von zwei Wochen vonstatten gehen sollte.

    Mit den Werkzeugen aus dem Artikel zu meiner Heizleistung von neulich kann mensch abschätzen, was so eine Konzentrationsschwankung in einer lokal gut durchmischten Atmosphäre in, sagen wir, verbranntem Kohlenstoff bedeuten würde.

    Dafür muss ich erst überlegen, wie viele CO2-Teilchen ΔNCO2, oder der Bequemlichkeit halber eher welche CO2-Stoffmenge ΔnCO2 = NCO2 ⁄ A („in mol”) es braucht, um die Konzentration (in ppm, also CO2-Molekülen pro Million Teilchen insgesamt) innerhalb eines angenommenen Volumens V um das Δcppm zu erhöhen, das ich aus dem Plot ablese. Gemäß meinen Rezepten von neulich ist das:

    ΔnCO2 = (V)/(Vm)⋅Δcppm⋅106, 

    wobei Vm wieder das Normvolumen ist (22.4 Liter pro mol); das A von oben war die Avogadro-Konstante. Um herauszukriegen, wie viel Kohlenstoff (sagen wir, in Kilogramm) ich verbrennen muss, um diese Änderung quasi durch „frisches“ CO2 hinzukriegen, muss ich das nur noch mit dem Atomgewicht von Kohlenstoff uC multiplizieren.

    Das Atomgewicht ist, weil Kohlenstoffkerne meist 6 Protonoen und 6 Neutronen enthalten, mit 12 g/mol gut abgeschätzt (ganz genau ist das nicht, vor allem weil in der Atmosphäre auch etwas C-13 und sogar ein wenig C-14 herumschwebt). In dieser Kopfzahl steht das Gramm aus historischen Gründen. Das Mol wurde so definiert, dass die Zahl der Nukleonen im Kern so in etwa das Atomgewicht liefert, als in der Wissenschaft das cgs-System (aus Zentimeter, Gramm und Sekunde) seine große Zeit hatte. Würde mensch das Mol in den heutigen SI-Zeiten (na gut: die meisten AstronomInnen bleiben dem cgs verhaftet und reden zum Beispiel über Energien in erg) definieren, wäre die Avogadro-Konstante um einen Faktor 1000 (nämlich den Faktor zur SI-Einheit Kilogramm) größer.

    Wie auch immer: Wenn ich mir mal vorstelle, dass das, was ich da auf meinem Balkon messe, repräsentativ für den Umkreis von 10 km und bis in eine Höhe von 2 km wäre (mensch ahnt schon: Ich eröffne hier eine Reductio ad absurdum), komme ich auf ein Volumen von

    V = 2⋅π⋅(10  km)2⋅(2  km) ≈ 1.3⋅1012  m3

    was mit Vm ≈ 0.02 m3 ⁄  mol, einer Änderung von 100 ppm, die mensch als Sprung am 9. und 10.11. sehen kann, sowie der Formel oben auf

    ΔmC  = uC(V)/(Vm)⋅Δcppm⋅106  ≈ 0.012 kg ⁄ mol(1.3⋅1012  m3)/(0.02 m3 ⁄  mol)⋅100⋅10 − 6  ≈ 8⋅107  kg

    oder achzigtausend Tonnen verbrannten Kohlenstoff führt. Das klingt nach richtig viel und ist es auch. Aber das Volumen, das ich hier betrachte, sind eben auch 1200 Kubikkilometer, und wer sich erinnert, dass ein Kubikmeter eines normalen Gase bei Normalbedingungen um die 1 kg wiegt, kann leicht ausrechnen, dass die Luft in diesem Volumen 1.2⋅1012  kg (oder 1.2 Milliarden Tonnen – Luft in großen Mengen ist überhaupt nicht leicht) wiegen wird. Dieser ganze Kohlenstoff macht also ungefähr 0.07 Promille (oder 70 Milionstel) der Masse der Atmosphäre aus, was ganz gut mit den 100 ppm in Teilchen zusammengeht, die wir in die ganze Rechnung reingesteckt haben.

    Andersrum gerechnet

    Tatsächlich kann mensch die Kohlenstoffmasse, die eine Erhöhung der Teilchenkonzentration in einem Gasvolumen bewirkt, auch so herum abschätzen. Der Umrechnungsfaktor von Teilchen- zu Massenkonzentration ist der Faktor zwischen den Dichten von CO2 und Luft. Das Verhältnis dieser Dichten ist wiederum das der jeweiligen Atommassen, solange jedes Teilchen das gleiche Volumen einnimmt; das schließlich folgt aus der Annahme, dass die Gase ideal sind, was wiederum für unsere Abschätzungen überallhin gut genug ist.

    Für CO2 ist das mit den überwiegend vorkommenden Isotopen von Sauerstoff und Kohlenstoff 16 + 16 + 12 = 44, für Luft, wenn wir nur auf den Stickstoff N2 schauen, 14 + 14 = 28. Demnach macht 1 ppm in der Teilchenzahl von CO2 44 ⁄ 28 ≈ 1.6 ppm in der Masse aus, solange die CO2-Konzentration so gering ist, dass tatsächlich N2 die Dichte dominiert.

    Andererseits macht Kohlenstoff nur 12 ⁄ 44 ≈ 0.3 an der Masse im CO2 aus, die Zunahme an Kohlenstoff ist demnach nur ein Drittel von dem gerade berechneten 1.6, also etwas wie 0.5. Folglich werden aus 100 ppm Änderung in der Teilchenzahl etwas wie 100⋅0.5 = 50  ppm Änderung in der Masse; wer das genauer rechnet, bekommt auf diese Weise natürlich das gleiche Resultat wie oben raus.

    Wie herum mensch das auch rechnet, es ist klar, dass niemand in der kurzen Zeit so viel Kohlenstoff verbrennt. Ein schneller Reality Check: Meine Kohlendioxid-Kopfzahl war, dass die BRD 2/3 Gigatonnen im Jahr emittiert, was mit dem C/CO2-Verhältnis von 0.3 von oben ungefähr 200 Megatonnen Kohlenstoff entspricht, oder irgendwas wie gut 500000 Tonnen am Tag. Damit wäre die Zunahme, die ich hier sehe, rund ein Sechstel des gesamten Kohlenstoffbudgets der BRD, und mehr, wenn der Anstieg schneller als in einem Tag vonstatten geht: Das ist (fast) natürlich Quatsch.

    Aber was ist es dann? Noch immer gefällt mir die These ganz lokaler Schwankungen nicht. Wenn hier wirklich nur das CO2 von Autos und Heizungen nicht mehr weggepustet würde, müsste die Korrelation zwischen CO2 und Wind viel deutlicher sein.

    Ist es eine die Abgasfahne des GKM?

    Nächster Versuch: Rund 12 km westlich von meiner Wohnung läuft das Großkraftwerk Mannheim („GKM“). Wenn das Volllast fährt und meine Wohnung in seine Abgasfahne kommt, könnte das so ein Signal geben?

    Nun, so ein Kraftwerk liefert ungefähr 1 Gigawatt elektrische Leistung (wie mir der Wikipedia-Artikel dazu gerade verrät: darunter 15% des deutschen Bahnstroms), was bei einem Wirkungsgrad von 1/3 (ok, bei modernen Kohlekraftwerken ist das noch ein wenig mehr, aber als Kopfzahl taugt es) auf 3 Gigawatt thermische Leistung führt (tatsächlich nennt die Wikpedia eine Bruttoleistung von 2146 MW für das GKM).

    Aus den 394 kJ/mol, die bei der Verbrennung von Kohlenstoff frei werden (vgl. den Artikel zu meiner thermischen Leistung) könnte mensch jetzt die CO2-Emission aus der Bruttoleistung ableiten, aber ich bin mal faul und sehe beim WWF nach, der für Kraftwerke dieser Größenordnung ansagt, für eine Kilowattstunde Strom (wir sind dann also wieder bei der Nutzleistung) werde rund ein Kilogramm CO2 emittiert.

    Wenn das Kraftwerk also Volldampf (rund ein GW netto) macht, wird es etwa

    109  W⋅0.001 kg ⁄ Wh = 106 kg ⁄ h

    CO2 emittieren, also etwa 1000 Tonnen, was wiederum mit unserem 0.3-Faktor zwischen Kohlenstoff und CO2 zu einem Kohleverbrauch von 300 Tonnen pro Stunde führt.

    Damit leert das Kraftwerk unter Vollast ein Großes Rheinschiff in zehn Stunden – das scheint mir zwar schon sehr schnell zu gehen, ist aber auch nicht gänzlich unplausibel. Gegenrechnung: Das WWF-Dokument von oben nennt 7.7⋅109  kg ⁄ a als CO2-Emission des GKM im Jahr 2006. Mit der Ur-Kopfzahl π ⋅ 1e7 Sekunden pro Jahr übersetzt sich das in eine mittlere Emission von etwa 200 kg pro Sekunde oder gut 1000 Tonnen pro Stunde. Das passt fast zu gut, denn als jemand, der das Kraftwerk von seiner Leseecke aus sehen kann, kann ich zuverlässig sagen, dass das Ding keineswegs durchläuft. Andererseits hatte das Kraftwerk 2006 auch noch einen Block weniger, und überhaupt ist in der Rechnung genug Luft für Stillstandszeiten.

    Nehmen wir …

  • Sicherheit, die wirklich niemand will

    Ich habe nie viel von dem Gerede von der „Balance von Sicherheit und Freiheit“ gehalten – so würde ich etwa behaupten, dass ohne eine gewisse soziale Sicherheit Freiheit ein recht hohler Begriff wird. Wer, sagen wir, unter permanenter Drohung durch die Hartz IV-Kautelen lebt, hat zumindest nicht mehr die Freiheit, sinnlose und miese Arbeit (Call Center, Lieferdienste, Burgerflippen) abzulehnen. Wenn nun die Gesellschaft auf absehbare Zeit nicht vom Arbeitszwang wegkommt, sind vermutlich nicht viele Zwänge (ja: Einschränkungen von Freiheit) demütigender als eine Lohnarbeit tun zu müssen, ohne einen Sinn in ihr zu sehen oder wenigestens Spaß an ihr zu haben.

    Aber gut: Die Leute, die gerne vom Widerspruch zwischen Freiheit und Sicherheit reden, haben sicher keine Freiheit zur Faulheit im Sinn, sondern eher die Freiheit, sich anderer Menschen zur eigenen Bereicherung zu bedienen. Auch ihre Sicherheit ist eine ganz andere als die von Existenz und Obdach. Ihre Sicherheit ist ziemlich genau das, das von Polizei, Militär und Überwachungstechnologie hergestellt, genauer: erzwungen werden kann. Erst bei diesem Erzwingen wird der Widerspruch von Freiheit und Sicherheit unausweichlich; er hängt damit aber klar an einem genz spezifischen Begriff von Sicherheit, den, wird er explizit gemacht, wohl nicht viele Menschen teilen werden.

    Ein gutes Beispiel, dass häufig gerade die „Geschützten“ diese Sorte Sicherheit gar nicht haben wollen, gab es am 24. Oktober im Hintergrund Politik des DLF: Jedenfalls offiziell zum „Schutz“ der auf Samos gestrandeten Geflüchteten findet im dort neu errichteten Lager eine strikte Eingangskontrolle statt. Die ist aber nur bis 20 Uhr besetzt. Das Lager ist außerdem am Ende der Welt, so dass Stadtausflüge am Nachmittag riskant werden. Ein Geflüchteter berichtet in der Sendung:

    Ich brauche [für den Weg zurück aus der Stadt] eine Stunde und 20 Minuten. Aber wenn du es nicht rechtzeitig zurückschaffst, lassen sie dich nicht mehr rein. Das ist mir schon passiert. Ich musste die ganze Nacht draußen verbringen. Im alten Camp haben wir zwar im Zelt gelebt, aber wir hatten unsere Freiheit.

    Grob in den Bereich passt etwas, auf das ich seit Wochen linken wollte, weil es wirklich lesenswert ist, nämlich die Stellungnahme von Amnesty International zum neuen Versammlungsgesetz in NRW. Ich glaube zwar nicht, dass irgendwer ernsthaft versucht, diesen Gesetzentwurf mit „Sicherheit” zu begründen. Es geht recht offensichtlich durchweg nur um autoritären Durchgriff („öffentliche Ordnung“). Dafür ist der Abbau von Grundrechten, die die Voraussetung von „Freiheit“ in jedem nicht völlig verdrehtem Sinn sind, hier aber auch besonders greifbar.

    Das sage nicht nur ich aus meiner linksradikalen Ecke. Selbst die sonst ja eher zurückhaltenden Leute von ai reden Klartext:

    Mit der Distanzierung von der Brokdorf-Entscheidung distanziert sich der Gesetzentwurf daher nicht nur von der Rechtsprechung des Bundesverfassungsgerichts, sondern auch von international verbindlichen Menschenrechtsstandards.

    Wie gesagt: Lohnende Lektüre für alle, die noch gerne einen Unterschied hätten zwischen den viel geschmähten „autokratischen Regimes“ und ihren eigenen Regierungen. Oder, sagen wir, den Verhältnissen in Spanien. Oder denen in Hessen.

  • Keine guten Nachrichten

    Und wieder muss ich meinen Hut essen im Zusammenhang mit meinen Corona-Zahlenspielen. Ich hatte nämlich vor neun Tagen zuversichtlich vorhergesagt, so etwa jetzt sollten knapp 3500 Intensivbetten in der BRD mit SARS-2-PatientInnen belegt sein, mit dem Argument, dass sich die entsprechenden Zahlen derzeit neun Tage hinter der Inzidenz herbewegen. Da (und das war, wie unten diskutiert, ein Fehlschluss) die Inzidenz in den neun Tagen vor dem 6.11. um 44% gestiegen war, sah ich die Intensivbelegung heute bei 2332⋅1.44 ≈ 3350. Tatsächlich aber berichtet das RKI heute von nur 3034 SARS-2 IntensivpatientInnen, also um die 10% weniger als meine Vorhersage – oder 30% weniger Anstieg, um die Fehleinschätzung mal deutlicher zu machen.

    Ein Metafehler und einige Nicht-Fehler

    Es war schon ein paar Tage abzusehen, dass ich falsch liegen würde, und ich habe mir bereits letzte Woche ein paar lose Gedanken gemacht, wo wohl mein Fehler liegen könnte. Nicht angreifen konnte ich meine Argumentation aus dem Artikel, nach der die Leute, die in den vergangenen neun Tagen intensivpflichtig geworden sind, damals bereits krank waren und in diesem Sinn nicht mehr viel zu ändern sein würde.

    Ich hatte dann kurz überlegt, ob vielleicht bei der Normalisierung der Ableitungen (das incs /= sum(abs(incs)) irgendwas schief gegangen sein kann. Aber nein, eine Angabe wie „44%“ ist natürlich selbst normalisiert („pro hundert“). Der Verdacht jedoch führte schon mal in die richtige Richtung: Nachdenken über die Ableiterei und was dabei so passiert.

    Bevor ich da weiterknoble, zunächst die eigenliche Selbstbezichtung, denn was ich vor neun Tagen zumindest hätte tun sollen, wäre eine simple Validierung an den bestehenden Daten, nämlich am unmittelbar vorhergehenden 9-Tage-Intervall. Am 27.10. war die Intensivbelegung bei 1707, in den neun Tagen vor dem 6.11. war die Intensivbelegung also um 37% gestiegen. Es wäre ganz leicht gewesen, gleich nachzusehen, ob auch die Meldezahlen des RKI in den neun Tagen davor um etwas wie 37% gestiegen sind. Ich hätte festgestellt, dass sie das nicht sind – am 27.10. lag die RKI-Meldeinzidenz bei 118, am 18.10. bei 74, ein Anstieg also um satte 59% –, und das hätte mir gesagt, dass ich einen Fehler gemacht habe.

    Auch dann hätte ich vermutlich, wie heute auch, den nächsten Verdacht auf die heftige Kontamination der tageweisen Inzidenzschätzungen des RKI durch Wochenenden und Co gelenkt – schon in meinem allerersten Corona-Post hatte ich die bejammert. Vielleicht ist es ja das? Im Programm von neulich glätte ich deshalb vor der Ableitung. Die geglättete Kurve kommt am 18.10. auf 75, am 27.10. auf 120, und für den 6.11. habe ich noch keine geglätteten Daten, weil da noch zu viele Randeffekte dabei sind. Das ist sehr nah an den ungeglätteten Daten. Also, nein: Das macht repariert meine Fehlvorhersage nicht.

    Der wirkliche Fehler

    Das tatsächliche Problem liegt in der Methode, und zwar nicht in dem komplizierten Teil. Die Berechnung des Verzuges mit all dem Glätten und Ableiten ist völlig in Ordnung. Das Problem ist vielmehr, und ein wenig Nachdenken über Schulmathematik hätte mich darauf bringen können, in der Natur der Ableitung. Bei der gehen Konstanten nämlich verloren: (d)/(dx)(f(x) + C) = (d)/(dx)f(x). Ein hoher Sockel von Langzeit-IntensivpatientInnen wird bei meiner Verzögerungsrechnung einfach wegdifferenziert. Das ist ja sogar der Sinn der Differenziererei.

    Nur: Wenn ich am Schluss blind „44% mehr“ rechne, wird der Sockel (das C) mitmultipliziert, und genau da wird es falsch. Die richtige Rechnung wäre gewesen, die Differenz der Inzidenzen über die neun Tage vor dem 27.10. (von 74 auf 118) zu vergleichen mit der Differenz der Intensivbelegung der neun Tage vor dem 6.11 (von 1707 auf 2332) – dabei geht der Verzug ein, irgendwelche konstanzen Sockel spielen aber keine Rolle.

    Dieser Vergleich ergibt einen, sagen wir, 9-Tage-Übersetzungfaktor von 625 ⁄ 44 ≈ 14. In diesem stecken die Demographie der Erkrankten, die Eigenschaften des Virus, das Verhalten der Bevölkerung, und alles andere, was die mittlere Wahrscheinlichkeit bestimmt, mit einer SARS-2-Infektion intensivpflichtig zu werden. Unter der Annahme jedoch, dass der Übersetzungsfaktor über kurze Zeiten in etwa kontant ist, kann mensch jetzt die Entwicklung korrekt vorhersagen. Und zwar übersetzt sich demnach die Inzidenzentwicklung zwischen 27.10. und 6.11. (von 118 auf 164) 14-fach in die Intensivbelegung der jetzt gerade vergangenen neun Tage (das ist letztlich etwas wie ein Momentanwert von meiner int/inc-Metrik aus dem September).

    Ich hätte damit am 6.11. vorhergesagt, die Intensivbelegung würde um 46⋅16 = 644 zunehmen oder eben auf 2332 + 644 = 2976, in guter Übereinstimmung mit dem berichteten Wert von 3034.

    Blöd, dass ich nach meinen Zahlen- und Interpolationsspielen beim Zusammenbau der Vorhersage nicht aufgepasst habe. Aber es zeigt mal wieder, dass Mathe voll ist mit Fallen und ein Moment der Unaufmerksamkeit ziemlich unausweichlich zu zwanghaftem Vertilgen von Hüten führt. Und dabei hätte ich mir durch einfache Versuche, die Zukunft der Verangenheit vorherzusagen – ein sehr probates Mittel, wann immer mensch Zeitreihen analysiert – diese wenig erfreuliche Mahlzeit sparen können. Rülps.

    Aus eine physikalischen Betrachtung heraus ist diese Methode auch nicht so arg befriedigend, denn natürlich gibts bei den Meldezahlen keinen Sockel. Die sind ja selbst schon Ableitungen[1], nämlich die der Gesamtzahl der Infizierten. Die Intensivbelegung ist von der Genese her noch komplexer, da dort Zu- wie Abgänge eingehen. Insofern ist die Sache mit dem Übersetzungsfaktor zutiefst phänomenologisch und kann also aus vielen Gründen brechen.

    Schauen wir also mal, wie es in neun Tagen, am 24.11., aussieht. Meine Vorhersage wäre 3034 + (303 − 184)⋅14 = 4700. Das ist auch von der Dynamik her nicht mehr weit weg von der Höchstbelegung am 3.1.2021 (5762), und ohne ziemlich deutliche „Maßnahmen” werden wir wohl recht bald an der vorbeirauschen.

    [1]Wobei: Solange die Entwicklung exponentiell ist, ist das mit der Ableitung in diesem Kontext quasi wurst, denn die Exponentialfunktion ex ist ihre eigene Ableitung. Reale Wachstumsfunktionen über der Zeit t sehen aus wie N(1 + r)t = Neln(1 + r)⋅t, wobei r die Wachstumsrate ist (mit RKI-Zahlen R-Wert minus 1). Die Ableitung solcher Funktionen sind sie selbst mal einem konstanten Faktor, und der würde bequem in unserem Übersetzungfaktor 14 aufgehen. Wie gesagt: alles erstmal phänomenologisch.
  • Fixing "No sandbox user" the Right Way

    I'm setting up an ancient machine – a Pentium M box with a meme 256 MB of RAM – with current Debian bullseye, and I'm impressed that that still works: this machine is almost 20 years old. Hats off to the Debian folks.

    But that's not really my story. Instead, this is about fixing what's behind the message:

    No sandbox user '_apt' on the system, can not drop privileges
    

    from apt. As you probably have just done, my first reaction was to feed that message to a search engine.

    Quite a few pages were returned, and all I looked at suggested to simply create the user using one of the many ways a Debian box has for that. That is not totally unreasonable, but it does not really address the underlying cause, and hence I thought I should do better.

    The immediately underlying cause is that for whatever deeper reason a maintainer script – shell scripts that Debian packages run after installing packages or before removing them – has not properly run; that is usually the place where packages create users and do similar housekeeping. Just creating the user may or may not be enough, depending on what else the maintainer script would have done.

    Hence, the better way to fix things is to re-run the maintainer script, as that would either run the full routine or at least give an error message that lets you figure out the deeper cause of the problem. Dpkg runs the maintainer script(s) automatically when you re-install the package in question.

    But what is that “package in question” that should have created the user? You could guess, and in this particular case your guess would quite likely be right, but a more generally applicable technique is to simply see what script should have created the user. That's not hard to do once you know that the maintainer scripts are kept (next to other package metadata) in /var/lib/dpkg/info/; so, with GNU grep's -r (recursive) option, you can run:

    grep -lr "_apt" /var/lib/dpkg/info/
    

    which gives the names of all files containing _apt in files below that directory. On my box, that is:

    /var/lib/dpkg/info/python3-apt.md5sums
    /var/lib/dpkg/info/libperl5.32:i386.symbols
    /var/lib/dpkg/info/apt.postinst
    /var/lib/dpkg/info/python3-apt.list
    

    Ah-ha! The string is mentioned in the post-installation script of the apt package. Peeking inside this file, you see:

    if [ "$1" = 'configure' ]; then
            # add unprivileged user for the apt methods
            adduser --force-badname --system --home /nonexistent  \
                --no-create-home --quiet _apt || true
    fi
    

    So: this really tries to create the user when the package is being configured, but it ignores any errors that may occur in the process (the || true). That explains why the system installation went fine and I got the warnings later (rather than a hard error during the installation).

    Just re-configuring the apt package would therefore be enough to either fix things or at least see an error message. But really, unless it's a huge package I tend to save on brain cycles and just run apt reinstall, which in this particular case leads to the somewhat funky command line:

    apt reinstall apt
    

    For me, this fixed the problem – and I've not bothered to fathom why the user creation failed during initial system setup. If you've seen the same problem and still have a record of the installation, perhaps you could investigate and file a bug if necessary?

  • Stemming for the Search Engine

    First off, here is a quick reference for the search syntax on this site (the search form links here):

    • Phrase searches ("this is a phrase")
    • Exclusions (-dontmatch)
    • Matches only when two words appear within 10 tokens of each other (matches NEAR appear)
    • Trailing wildcard as in file patterns (trail*)
    • Searches don't use stemming by default, but stem for German when introduced with l:de and for English when introduced with l:en
    • See also the Xapian syntax.

    If you only came here for the search syntax, that's it, and you can stop reading here.

    Otherwise, if you have read the previous post on my little search engine, you will remember I was a bit unhappy that I completely ignored the language of the posts and had wanted to support stemming so that you can find, ideally, documents containing any of "search", "searches", "searching", and "searched" when searching for any of these. Being able to do that (without completely ruining precision) is obviously language-dependent, which means the first step to make it happen is to properly declare the languague of your posts.

    As discussed in the previous post, my blogsearch script only looks at elements with the CSS class indexable, and so I decided to have the language declaration there, too. In my templates, I hence now use:

    <div class="indexable" lang="{{ article.lang }}">
    

    or:

    <div class="indexable" lang="{{ page.lang }}">
    

    as appropriate.

    This is interpreted by the indexer rather straightforwardly by pulling the value out of the attribute and asking xapian for a stemmer for the named language. That works for at least most European two-letter country codes, because those happen to coincide with what's legal in HTML's lang universal attribute. It does not work for the more complex BCP 47 language tags like de-AT (where no actually existing stemmer would give results different from plain de anyway) or even sr-Latn-RS (for which, I think, no stemmer exists).

    On searching, I was worried that enabling stemming would blow unstemmed searches, but xapian's indexes are clever enough that that's not a problem. But I still cannot stem queries by default, because it is hard to guess their language from just a word or two. Hence, I have defined a query syntax extension: If you prefix your query with l:whatever, blogsearch will try to construct a xapian stemmer from whatever. If that fails, you'll get an error, if it succeeds, it will stem the query in that language.

    As an aside, I considered for a moment whether it is a terribly good idea to hand through essentially unfiltered user input to a C++ API like xapian's. I eventually settled for just making it a bit harder to craft buffer overflows by saying:

    lang = parts[0][2:30]
    

    – that is, I'm only allowing through up to 28 characters of language code. Not that I expect that anything in between my code and xapian's core has an overflow problem, but this is a cheap defensive measure that would also limit the amount of code someone could smuggle in in case some vulnerability did sneak in. Since it's essentially free, I'd say that's reasonable defensive programming.

    In closing, I do not think stemmed searches will be used a lot, and as usual with these very simple stemmers, they leave a lot to be desired from a linguistic point of view. Compare, for instance, a simple search for going with the result l:en going to see where this is supposed to go (and compare with the result when stemming as German). And then compare with l:en went, which should return the same as l:en going in an ideal world but of course doesn't: Not with the simple snowball stemmer that xapian employs.

    I'm still happy the feature's there, and I'm sure I'll need it one of these days.

    And again, if you need a CGI that can index and query your static HTML collection with low deployment effort: you're welcome.

  • Ad hominem 2

    Panorama des Stuttgarter Schlosses

    Einst die Residenz des Herrn Schmid, der nun die Ukraine[1] als Zwischenlager ihm unwillkommener Menschen nutzen möchte: Das Stuttgarter Schloss (CC-BY-SA Grossmummrich).

    Wie ich neulich schon betonte, ist mir selbstverständlich klar, dass im politischen Diskurs Attacken auf fragwürdige charakterliche oder physische Eigenschaften von MachthaberInnen sowie Menschen, die es werden wollen, als unfein gelten.

    Andererseits gehen die guten Sitten™ offensichtlich auch ohne mich vor die Hunde. Aktuelles Beispiel: Der „SPD-Außenpolitiker“ (Deutschlandfunk) Nils Schmidt hat gestern im DLF gefordert, die Menschen, die gerade über Belarus in die EU fliehen, sollten doch in der Ukraine quasi zwischengelagert (eingestanden: das ist nicht Schmids Wort, aber doch nach Bedeutung und Stil das, was er sagt) werden, und zwar im Wesentlichen aus Gründen des Prinzips.

    Er hat sich so nicht nur an dem furchtbaren Diskurs beteiligt, die Fliehenden seien irgendwie eine Waffe oder eine Bedrohung, sondern macht auch die Ansage, er halte die Ukraine – ganz wie die russische Regierung übrigens – für so eine Art Kolonie der EU, über die diese nach ihrem Belieben verfügen kann. Und so fühle ich mich aufgerufen, an eine Geschichte zu erinnern, bei der Schmid vor seiner Zeit als „Außenpolitiker“ in meiner weiteren Umgebung unangenehm aufgefallen ist.

    Bevor er nämlich „Außenpolitiker“ wurde, war er Finanzminister der ersten Grün-Roten Regierung hier in Baden-Württemberg. Als solcher residierte er im Stuttgarter Schloss. Wenn ihr dem Wikipedia-Link folgt, lest ihr dort:

    Seit dem Auszug des Ministeriums für Kultus, Jugend und Sport Anfang 2012 ist es heute noch Sitz des Finanzministeriums [...]

    Der Auszug des Kultusministeriums hat mit Schmid zu tun – seine Amtszeit als Finanzminister begann 2011. Zumindest im Umfeld der Verdrängten ging damals die Geschichte um, das Schloss sei zu CDU-Zeiten zwar groß genug für zwei Ministerien gewesen, für Schmid, sein Ego und das Kultusministerium reiche es aber nicht. Da die CDU-Ära Kultusministerinnen wie Dr. Annette Schavan und Finanzminister wie Gerhard Mayer-Vorfelder – beide gewiss keine Kinder der Bescheidenheit – kannte, fällt es nicht leicht, das zu glauben, aber es ist zweifellos plausibel, dass Schmid nach seiner Wahlniederlage gegen die Grünen einiges zu kompensieren hatte und sich das ganze Schloss sozusagen zum Trost gönnte.

    Ja, das ist alles etwas ad hominem, aber wer im Namen von „Recht und Ordnung“ austeilt wie Schmid in dem DLF-Interview, sollte auf Rückfragen zur eigenen Charakterfestigkeit zumindest vorbereitet sein.

    Bei alldem gestehe ich gerne, dass mich der Furor des Herrn Schmid besonders befremdet, weil er gerade jetzt kommt. Lukaschenko ist sicher neben vielem anderen vorzuwerfen, dass er viele Jahre lang in das schmutzige Grenzregime der EU verstrickt war; im 2019er-Bericht zu Abschiebungen aus der EU heißt es etwa zu Weißrussland:

    A total of 18 Member States reported having approached the authorities of Belarus for readmission matters [das ist hier Schurkensprache für „Abschiebung“] related to its nationals in 2019.

    All of them assessed the overall cooperation with Belarus in the identification procedure as good or very good (except one which rated it as average).

    This is reflected in 13 Member States having a functioning established routine [für Abschiebungen] with Belarus diplomatic missions, with only one informing that it is not effective.

    Gut: die da abgeschoben wurden, waren Menschen mit belarusischen Pässen, aber auch die sollte mensch wohl nicht in ein Land abschieben, das im öffentlichen Diskurs recht konsistent als „letzte Diktatur Europas“ gehandelt wurde (if only it were true). Jedenfalls nicht, wenn mensch anschließend irgendwelche menschenrechtlichen Standards an andere anlegen will.

    Angesichts dieser Geschichte der Kollusion beim Grenzregime Lukaschenko ausgerechnet dann Verfehlungen vorzuwerfen, wenn er sein Land erstmal nicht mehr als extrabreite Grenzmauer der EU zur Verfügung stellt, das lässt tief blicken im Hinblick auf die Umsetzung eines Ziels, das in der DLF-Presseschau vom 10.12. mit wirklich schockierender Ehrlichkeit aus der Nordwest-Zeitung zitiert wurde:

    Viertens ist auf EU-Ebene eine kollektive Einwanderungspolitik, die sich ausschließlich an den Interessen Europas ausrichtet, noch immer überfällig.

    Raubt nur mir den Atem, wie komplett der Kommentator hier vergessen hat, was das Wort „Asylrecht“ bedeutet[2]? Dass es darum ging, Menschen, die vor Krieg und Verfolgung fliehen müssen, eine Möglichkeit dazu zu geben?

    Wer so etwas schreibt, ist sich offensichtlich sehr sicher, niemals fliehen zu müssen. Diese Sicherheit sei solchen Leuten gegönnt, und wer mit so viel Patriotismus und Staatsraison gesegnet ist, mag mit der Zuversicht auch richtig liegen. Daraus aber zu schließen, dass auch alle anderen nicht fliehen dürfen, das ist, ich kann es nicht anders sagen, ein klares Merkmal von Schurken.

    Doch ich will versöhnlich enden, denn die Presseschau am nächsten Tag schloss mit folgender Einsicht aus dem Freitag (den natürlich wieder niemand liest):

    Was bei aller Lukaschenko-Verteufelung in vielen Zeitungen vergessen wird: Das ist nicht der Grund, warum diese Leute aus dem Nahen Osten kommen. Sie fliehen vor Kriegen und kriegerischen Folgeschäden aus dem Irak, Syrien oder Afghanistan. Die zugrunde liegenden Konflikte, die ihre Heimat destabilisierten, fanden ohne Beteiligung von Belarus, sehr wohl aber unter maßgeblichem Mitmischen des Westens, darunter EU-Mitgliedern, statt.

    Das hätte ich nicht schöner sagen können.

    [1]Das gerade, während in der Ukraine Corona durch schwindelerregend schnell durch eine offenbar nicht gut immunisierte Bevölkerung läuft: der z-Score (in etwa: Wie viele Standardabweichungen liegt die derzeitige Sterblichkeit über dem langjährigen Mittel?) war dort laut Euromomo vor drei Wochen bei fast 23, in Worten „extraordinarily high“. Aktuellere Zahlen sind wohl wegen Meldeverzug noch sehr unzuverlässig.
    [2]Na gut: Das ist, was Asylrecht bedeutet hat, bevor der Bundestag am schicksalsschweren 26.5.1993 mit dem Artikel 16a Grundgesetz das Asylrecht in der BRD in eine hohle, winddurchpfeifte Ruine verwandelt hat. Zur Erinnerung: Damit wurde die „Drittstaatenregelung“ eingeführt, die, auf EU-Ebene skaliert (u.a. „Dublin II“), letztlich der rechtliche Hintergrund der gespenstischen Vorgänge an der Grenze zwischen Belarus und Polen ist. Aber das ist nochmal eine ganz andere Geschichte.
  • Moving Clipboard Content Between Displays and Machines with Xclip

    Since Corona started, I've had to occasionally run zoom and other questionable telecon software. I don't want that proprietary junk on my main machine, partly because I'm a raving Free software lunatic, partly because binary packages from commercial vendors outside of the Debian main repository have a way of blowing up things years after one has put them on a box. I take some pride in never having re-installed my primary machine since 1996, so there would have been lots of opportunity for binary junk to accumulate.

    Hence I took a spare box I had sitting around idly, quickly put a simple Debian on its disk and then dumped all the questionable proprietary code next to its systemd and pulseaudio, reckoning that shredding that file system once the zoom pandemic is over will give me a lot of satisfaction.

    But now the various links, room ids and whatnot come in on the proper machine. Until a few days ago, I used to move them over to the zoom machine by having a screen open there, ssh-ing in from my main box, running screen -x to attach the screen that is already running in the ssh session, and then pasting the link into that shared screen. It works, but it feels clunky.

    The other day, I finally realised there's a better way using a nifty thing called xclip. I had already used xclip for ages whenever I have two displays running on a single box and I need to copy and paste between the two displaye; that happens when I'm at work. Then, I use the following key bindings (in this case for sawfish) on both ends:

    (bind-keys global-keymap "M-C-v"
            '(system "xclip -in < ~/.current-clipboard"))
    (bind-keys global-keymap "M-C-c"
            '(system "xclip -out > ~/.current-clipboard"))
    

    This lets me hit Alt-Ctrl-C on the first display and Alt-Ctrl-V on the second, and I'll then have what was in the primary selection on the first in the primary selection on the second.

    When later webkit on gtk3 started to copy links into the X11 clipboard rather than the primary selection and I wanted a quick way to get them to where I can middle-mouse them in again, I added another xclip binding to my sawfshrc:

    (bind-keys global-keymap "M-RET"
      '(system "xclip -out -selection clipboard | xclip -in"))
    

    – that's Meta-Return copying the content of the clipoard to the primary selection, and I've come to use that one quite extensively after initially piling quite a bit of abuse on the gtk3 policy of using the clipboard.

    What I noticed the other day was that xclip also lets me conveniently transport the telecon links. I've created an alias for that:

    alias zoomclip='xclip -o | ssh zoom "DISPLAY=:0 xclip -r -l 1 -i"'
    

    (zoom here is the name of the target machine). My new workflow is: select the string to transmit, run zoomclip in a terminal, hit the middle mouse button on the target machine to paste what I selected on the source machine. I'm not sure if it saves a lot of time over the old screen-based method, but it sure feels niftier, and I'd say that's reason enough for that alias.

    Note that the DISPLAY=:0 in the remote command is necessary because xclip of course is a normal X client and needs to know what display to talk to; and you want the local display on the target machine, not the display on the source machine. The -l 1, on the other hand, makes the xclip on the remote machine exit once you have pasted the content. Leave the option out if you expect to need to paste the thing multiple times. But without the -l 1, due to the way the selections are built on X11 (i.e, the system doesn't store selection content, you're always directly sending stuff between clients), xclip runs (and hence the ssh connection is being maintained) until some other client takes over the selection.

  • Der hundertste Post

    Vor 10 Monaten habe ich den ersten Artikel für dieses Blog geschrieben, und siehe da: Mit diesem sind es jetzt 100 Posts geworden.

    Das wäre ein guter Vorwand für ein paar Statistiken, aber da ich ja generell ein Feind von Metriken bin, die mensch ohne konkrete Fragestellung sammelt (das ist ein wenig wie beim statistischen Testen: Wenn du nicht von vorneherein weißt, worauf du testest, machst du es falsch), bestätige ich mir nur, dass meine Posts viel länger sind als ich das eigentlich will. Insgesamt nämlich habe ich nach Zählung von wc -l auf den Quelldateien fast 93000 Wörter in diesen Artikeln. Zur Fehlerabschätzung: xapian (vgl. unten) zählt nur 89000.

    Die Länge der Artikel ist nach wc-Wörtern so verteilt:

    Histogramm mit einem Klumpen zwischen 200 und 1000 und einem Outlier bei 3000

    Ich weiß auch nicht recht, warum ich mich nicht kürzer fassen kann. Oder will. Der überlange Post mit 3244 Wörtern ist übrigens der über die Konfiguration eines Mailservers – und das ist wieder ein gutes Beispiel für die Fragwürdigkeit von Metriken, denn erstens hat Englisch fast keine Komposita und ist von daher im Nachteil beim Wörterzählen und zweitens ist in dem Artikel ziemlich viel Material, das in Wirklichkeit Rechner lesen, und das sollte wirklich anders zählen als natürlichsprachiger Text.

    Na gut, und einem Weiteren kann ich nicht widerstehen: Wie viele verschiedene Wörter („Paradigmata“) kommen da eigentlich vor? Das ist natürlich auch Mumpitz, denn die Definition, wann zwei Wörter verschieden sind („die Token verschiedenen Paradigmata angehören“), ist alles andere als tivial. So würde ich beispielsweise behaupten, dass die Wörter Worte und Wörter praktisch nichts miteinander zu tun haben, während im Deuschen z.B. auf, schaute und aufschauen besser alle zusammen ein einziges Paradigma bilden sollten (zusammen mit allerlei anderem).

    Aber ist ja egal, sind ja nur Metriken, ist also eh Quatsch. Und es gibt die Daten auch schon, was für die Nutzung von und die Liebe zu Kennzahlen immer ein Vorteil ist. Ich habe nämlich den xapian-Index über dem Blog, und mit dem kann ich einfach ein paar Zeilen Python schreiben:

    import xapian
    db = xapian.Database("output/.xapian_db")
    print(sum(1 for w in db.allterms()))
    

    (Beachtet die elegante Längenbestimmung mit konstantem Speicherbedarf – db.allterms() ist nämlich ein Iterator).

    Damit bekomme ich – ich stemme nach wie vor nicht – 16540 raus. Klar, diese 16540 für die Zahl der verschiedenen Wörter ist selbst nach den lockeren Maßstäben von Metriken ganz besonders sinnlos, weil es ja eine wilde Mischung von Deutsch und Englisch ist.

    Um so mehr Spaß macht es, das mit den 100'000 Wörtern zu vergleichen, die schließlich mal im Goethe-Wörterbuch sein sollen, wenn es fertig ist. Eine schnelle Webrecherche hat leider nichts zur Frage ergeben, wie entsprechende Schätzungen für Thomas Mann aussehen. Einmal, wenn ich gerne Kennzahlen vergleichen würde…

  • Tatort und Menschenrechte

    Ich bekenne, regelmäßiger Tatort-Konsument zu sein und rechtfertige das gerne mit einem Interesse an der Öffentlichkeitsarbeit der Polizei, auch wenn die Danksagungen an diverse Polizeien in den Abspannen seltener geworden sind (oder jedenfalls scheint mir das so). Aber lasst mir mal die Annahme, dass bei den Drehbüchern polizeiliche PressesprecherInnen dann und wann mitreden.

    Mit dieser Annahme verblüfft mich, in welchem Maße die Filme zu einer Demo für Überwachungstechnologie verkommen sind. Seht euch mal im Vergleich irgendeinen alten Derrick an, oder wegen mir auch einen Felmy-Tatort aus den 70ern: Nicht, dass die plausibler oder näher an der damaligen Realität gewesen sein werden, aber das äußerste, was dort an Technik aufscheint, ist vielleicht ein mal ein Fingerabdruck, und wenns ganz scary werden soll, ein Nachschlagen in einer Straftäterdatei über ein grün flimmerndes Terminal. Ansonsten: Verhöre, Zeugengespräche, ernst guckende Beamte in braunen Anzügen.

    Filmszene: ein eine gelbe Spur auf einem Stadtplan

    Aus dem Tatort „Dreams“ vom letzten Sonntag: die Ermittler diskutieren ein in der Erzählung retrograd (nämlich etwas wie drei Tage nach der mutmaßlichen Tat) abgerufenes „Bewegungsprofil“ einer Verdächtigen. Wie sich Tatort-AutorInnen das halt so vorstellen. Und vielleicht auch die Münchner Polizei? (Bildrechte bei der ARD)

    Natürlich ist in der Zwischenzeit auch der reale Polizeialltag deutlich techniklastiger geworden, und das muss ein Fernsehkrimi nicht unbedingt ignorieren. Aber: Im Wesentlichen jeden Fall um DNA-Abgleiche oder -Analysen, Telefon-Verbindungs- und Standortdaten, haufenweise Videodaten (übrigens: im Hinblick auf private Kameras hat das erst seit 2017 überhaupt eine Rechtsgrundlage in der StPO), mehr oder weniger legale Zugriffe auf allerlei weiteren Computerkram (den nächsten Tatort, in dem die Kommissäre scharfsinnig Passwörter raten, schalte ich ab) und etliche weitere forensische Methoden aus der dystopischen Zukunft herumzustricken, das reflektiert sicher keine Realität. Das kann ich schon allein in Kenntnis aktueller Klageschriften und den in (wenn auch vor allem politischen) Verfahren tatsächlich verwendeten Beweismitteln zuversichtlich sagen.

    Vor allem verblüfft dabei, was für völlig bürgerrechtsfeindliche Vorstellungen der durchschnittliche Tatort inzwischen transportiert. Da hängen überall aufzeichnende Kameras rum, die auch noch fröhlich öffentliche Räume abbilden[1] , da reicht ein Blick in den Computer für eine halbwegs vollständige Biographe offenbar beliebiger Personen, und dann gibts den ganzen Mythos rund um das, worum es bei der Vorratsdatenspeicherung wirklich geht.

    Der Screenshot oben aus dem Tatort vom Sonntag ist ein besonderes absurdes Beispiel. Da haben die Ermittler ein „Bewegungsprofil“ bestellt – wo genau, bleibt unklar – und bekommen dann tatsächlich etwas, das die Qualität eines GPS-Tracks hat. Das ist glücklicherweise Unfug. Noch nicht mal bei einem Live-Abruf von Standortdaten nach §100i StPO kämen Geodaten dieser Sorte heraus; im GSM-Netz schon gar nicht, und selbst mit LTE und 5G sind Zellen mindestens einige Hektar groß und eine Triangulation – so sie überhaupt gemacht wird – ist in der Stadt Glückssache. Klar, mit einem GPS-Tracker (für den gibt es, im Gegensatz zum Zugriff auf die GPS-Daten des Mobiltelefons außerhalb der „Online-Durchsuchung“, eine Rechtsgrundlage) ginge das, aber den gab es am Auto der Verdächtigen in dieser Geschichte nicht.

    Im Nachhinein, also wie in diesem Fall sagen wir nach drei Tagen, kann die Polizei allenfalls auf Verkehrsdaten nach §96 TKG hoffen. Da sind zwar auch Standortdaten dabei, aber eben nur zu „Beginn und […] Ende der jeweiligen Verbindung“ – die Herstellung einer solchen Verbindung war Zweck der „Ortungspulse“ der stillen SMS. Nun ist das mit der „Verbindung“ im Zeitalter von paketvermittelten Diensten ein etwas schwieriger Begriff, und ich gestehe, auch nicht aus erster Hand zu wissen, wie das in der Praxis interpretiert wird. Klar ist aber, dass nicht alle paar Sekunden so ein §96 TKG-Record entsteht, um so mehr, als die der Norm zugrundeliegende Vorratsdatenspeicherung ja ausgesetzt ist[2].

    Also: Warum erfinden die Tatort-AutorInnen eine Aluhut-Welt und warum rät ihnen die Polizei nicht davon ab, rechnend, dass die ZuschauerInnen es gruselig finden könnten, wenn die Polizei auf Knopfdruck noch nach Wochen ihre Wege so genau nachvollziehen könnte? Und wie ist das mit den ZuschauerInnen selbst? Haben die inzwischen so viel Angst, dass sie in der Breite so eine dystopische Vision als „na ja, wenns der Sicherheit dient“ gerne ansehen?

    Sachdienliche Hinweise werden bei der Kontaktadresse dankbar angenommen.

    [1]In jedem zweiten Fall wird das Videomaterial dann auch noch hochgezoomt, als wäre es die slippy map auf osm.org. Ich glaube, diese Albernheit hat sich inzwischen zu einem Meme entwickelt. Läuft das unter dem Label „Navy CIS”?
    [2]Die Frage von oben, „wo genau“ die Polizei das „Bewegungsprofil“ her hat, könnte hier interessant werden, denn es ist vorstellbar, dass Google bei hinreichend unvorsichtig konfigurierten Telefonen Daten dieser Genauigkeit vorhält. Wenn das so ist, wäre es wirklich spannend, Statistiken über den staatlichen Zugriff auf diese Bestände zu bekommen. Ich sollte dazu mal sorgfältig https://lumendatabase.org (so heißen die chilling effects von einst inzwischen) lesen.
  • Halbwegs gute Nachrichten

    Eine Kurve mit einem breitem Minimum um die 8.5

    Aus dieser Kurve lässt sich ablesen, dass wir in acht Tagen knapp 3500 SARS-2-Fälle auf Intensivstationen haben werden. Wie, verrate ich in diesem Artikel.

    In meinen Corona-Überlegungen gestern habe ich mich mal wieder gefragt, wie lange wohl die „Intensiv-Antwort“ dem Inzidenzsignal nachläuft, wie viele Tage es also dauert, bis sich ein Anstieg in den Inzidenzen in der Intensivbelegung reflektiert. Diese Frage ist, wie ich unten ausführe, derzeit ziemlich relevant im Hinblick auf Überlegungen, wie lange wir eigentlich noch Zeit haben, um massenhafte Triage in (oder vor) unseren Intensivstationen abzuwenden.

    Meine Null-Annahme für diese Verzögerung war seit Mai 2020 – nach einer entsprechenden Ansage eines befreundeten Anästhesisten – „eher so drei Wochen“. Bei nährem Nachdenken ist mir gestern aber aufgefallen, dass er wohl eher „von Infektion bis Intensiv“ gemeint haben wird, und dann ist die Antwort sicher schneller, denn von Infektion bis Meldung vergeht normalerweise wohl mindestens eine Woche. Aber: Ich muss nicht raten. Wir spielen hier mit Kennzahlen, die vielleicht in der Realität nicht immer viel bedeuten, aber zumindest klar definiert sind. Daher lässt sich der Verzug auf der Basis von RKI- und DIVI-Zahlen nachrechnen.

    Ich verrate gleich mal das Ergebnis: ich komme für die zweite Welle auf gut fünf Tage Verzug der Intensiv-Antwort, für die dritte auf etwa sieben Tage, für die vierte Welle auf acht bis neun Tage. Wie ich unten ausführe, sind das relativ gute Nachrichten.

    Zeitreihen

    Wie habe ich das gerechnet? Nun, die Korrelation von Zeitreihen ist ein ganzer Satz von Wissenschaften, bei denen es meist darum geht, ungleiche Abdeckungen, unregelmäßig gesetzte Messpunkte sowie allerlei Rauschen und Schmutz weggefummelt zu kriegen, ohne allzu viele Informationen zu verlieren oder, vielleicht schlimmer, damit Artefakte einzubauen.

    Die so gereinigten Daten lassen sich dann zum Beispiel geeignet skaliert und verschoben übereinanderlegen. Tatsächlich sind die Parameter dieser Transformationen im Regelfall (und gewissermaßen auch hier) viel interessanter als die Zeitreihen selbst[1]. Es gibt daher zahlreiche mathematische Verfahren, die das von einer Augenmaß-Übung in etwas verwandeln, das reproduzierbar und auch quantifizierbar ist. Vorsicht: ich bin da kein Experte und gehe hier nur mit nicht allzu schwer erkranktem Menschenverstand ran, verwende also (zumindest in Summe) gerade kein wirklich wohldurchdachtes Verfahren. Wer sowas wie das hier für Hausaufgaben oder Hausarbeiten verwendet, tut das auf eigene Gefahr.

    Andererseits bin ich recht zuversichtlich, dass der Kram insgesamt schon stimmt.

    Ausgangsdaten sind meine aus RKI-Berichten gescrapten Intensivbelegungen und ein RKI-Sheet (in, ach weh, „Office Open XML“ a.k.a. XSLX – muss das sein?) mit den Inzidenzen. Dabei ist das erste Problem, dass ich aus verschiedenen Gründen nicht für jeden Tag Belegungszahlen habe, und so ist mein erster Schritt, fehlende Punkte zu interpolieren. Das Scipy-Paket macht es leicht, aus einem Satz von Zeit/Wert-Paaren (die Zeit wird in den Lesefunktionen auf Sekunden seit einer Epoche gewandelt) ein handliches Array zu rechnen:

    grid_points = numpy.arange(
      raw_crits[0][0],
      raw_crits[-1][0],
      86400)  # ein Tag in Sekunden
    critnums = interpolate.griddata(
      raw_crits[:,0], raw_crits[:,1], grid_points)
    

    Ich wollte diese Interpolation eigentlich visualisieren, habe aber keine gute Stelle gefunden, an der sie einen sichtbaren Unterschied gemacht hätte, und entscheidend ist eigentlich nur, dass ich ab diesem Schritt blind mit Arrays arbeiten kann; deren Index ist zunächst die Zahl der Tage seit dem ersten Tag mit Daten, hier speziell dem 11.8.2020, denn damals habe ich mit dem Screenscrapen der DIVI-Daten angefangen.

    Übergeplottete Kurven

    Wenn ich diese interpolierten Kurven übereinanderlege, kommt das hier heraus:

    Zwei nicht so arg gut aufeinanderpassende Kurven

    Weil das, wie gesagt, erst im August 2020 anfängt, fehlt die erste Welle.

    Das bloße Auge reicht für die Bestätigung der Erwartung, dass die Intensivbelegung der Inzidenzkurve meist etwas hinterherläuft, wenn auch nicht so, dass mensch hoffen könnte, die beiden durch etwas Schieben global übereinanderzubekommen. Die großen Zacken in den Inzidenzen durch Weihnachten und Ostern finden sich in der Intensivbelegung gar nicht wieder, was ein klares Zeichen ist, dass sie weitgehend Erfassungsartefakte sind. In der Hinsicht wären die „großen“ RKI-Zahlen mit Referenzdaten (vgl. die Film-Geschichte) bestimmt besser, aber ich wollte für dieses Ding nicht die 200 Megabyte durchkämmen, zumal die „kleinen“ RKI-Daten besser auf die Meldedaten aus den Tagesberichten passen, um die es mir hier ja geht.

    Vor allem fällt auf, dass meine Jammerei darüber, dass der Impffortschritt die Intensivantwort nicht wesentlich abgeflacht hat, unzutreffend ist: Mit der Skalierung aus dem Plot liegen Inzidenz und Belegung in der zweiten und dritten Welle ziemlich übereinander, während in der vierten Welle doch ein knapper Faktor zwei dazwischenliegt; da scheint die Impfung doch ein wenig gegenüber Delta zu gewinnen (aber, klar, nirgendwo hinreichend).

    Ich hatte erwartet, dass die abfallenden Flanken der Intensivkurven deutlich flacher sind als die der Inzidenzkurven, weil Leute unter Umständen lang auf Intensiv liegen und es entsprechend lang dauern sollte, bis sich die Stationen wieder leeren. Das sieht im Abfall der zweiten Welle auch ein wenig so aus, nicht jedoch bei dem der dritten Welle. Bei ihr fällt die Intensivbelegung sehr treu mit der Inzidenz. So viele LangzeitpatientInnen gibt es glücklicherweise wohl doch nicht.

    Nach Glätten differenzierbar

    Wie kann ich jetzt den Verzug zu quantifizieren? Mein (wie gesagt eher intuitiv gefasster) Plan ist, über die Ableitung der jeweiligen Kurven zu gehen, und zwar aus der Überlegung heraus, dass mich ja Veränderungen viel mehr als Pegel interessieren. Nun habe ich aber keine differenzierbaren Funktionen, sondern lediglich Arrays, bei denen ich Ableitungen allenfalls durch Subtrahieren benachbarter Elemente simulieren kann. Solche numerischen „Ableitungen“ reagieren ziemlich empfindlich auf das Gewackel („Rauschen“), das es in realen Daten immer gibt („Subtraktion ist in der Regel numerisch schlecht konditioniert“). Deshalb will ich meine rohen Daten glätten, bevor ich die „Ableitung“ ausrechne, sprich: das Rauschen rausnehmen, ohne das Signal wesentlich zu verzerren.

    Glättung heißt eigentlich immer, Kurvenpunkte in einem Zelle für Zelle über die Daten laufenden Fenster zu mitteln, also z.B., indem mensch je fünf Nachbarpunkte rechts und links auf den aktuellen Punkt draufaddiert und ihn dann durch die durch elf geteilte Summe ersetzt. Mit diesem ganz naiven Rezept bekommen relativ weit entfernte Werte aber genauso viel Einfluss auf den aktuellen Punkt wie die unmittelbaren Nachbarn. Das modelliert meist die sachlichen Grundlagen des Rauschens nur schlecht. Es ist auch aus theoretischeren Gründen normalerweise eher ungünstig.

    Der eher theoretische Hintergrund ist grob, dass bei der Glättung letztlich zwei Funktionen gefaltet werden. Das ist äquivalent dazu, die Spektren der beiden Signale (ihre Fouriertransformierten) zu multiplizieren und dann wieder zurückzutransformieren. Bei einem einfachen Fenster des oben skizierten Typs („Rechteckfunktion“) gibts nun sehr scharfe Kanten, die wiederum zu einem sehr breiten Spektrum führen, so dass auch das Spektrum der geglätteten Funktion allerlei unwillkommene Features bekommen kann (manchmal ist das aber auch genau das, was mensch haben will – hier nicht).

    Wie auch immer: scipy macht es einfach, mit einer lärmarmen Funktion – die sieht ein wenig gaußig aus und ist im nächsten Plot im kleinen Inset zu sehen – zu glätten, nämlich etwa so:

    import numpy
    from scipy import signal
    
    smoothing_kernel = numpy.kaiser(20, smoothing_width)
    smoothing_kernel = smoothing_kernel/sum(smoothing_kernel)
    convolved = signal.convolve(arr, smoothing_kernel, mode="same"
      )[smoothing_width:-smoothing_width]
    

    Die Division durch die Summe von smoothing_kernel in der zweiten Zeile sorgt dafür, dass sich an der „Höhe“ der geglätteten Funktion insgesamt nichts ändert: Sozusagen ausgeklammert ist die Faltung eine Multiplikation mit eins. Das Wegschneiden der Ränder in der letzten Zeile wiederum entfernt Punkte, bei denen fehlende Werte am Anfang und Ende der Zeitreihe im Fenster waren. Scipy ersetzt die durch Nullen, so dass die geglätteten Werte am Rand steil abfallen. Was ich hier mache, entspricht technisch dem valid-Mode der convolve-Funktion. Nur weiß ich hier zuverlässig, wie lang das Array am Schluss ist.

    Der Effekt, hier auf Indzidenzdaten der vierten Welle:

    Zwei Kurven, eng beieinander, aber eine viel rauschiger, mit einem glockenähnlichen Inset

    Damit kann ich jetzt meine numerische „Ableitung“ bilden, ohne dass mir das Ergebnis furchtbar rauscht:

    diff = convolved[1:]-convolved[:-1]
    

    Die nächsten beiden Grafiken zeigen diese Pseudo-Ableitungen für Inzidenz und Intensivbelegung. Ich habe jeweils in blau reingemalt, wie es ohne Glättung aussehen würde, um deutlich zu machen, warum diese eine gute Idee ist und was ich mit „furchtbar rauschen“ meine. Weiterverwendet werden natürlich die orangen Verläufe:

    Zwei Kurven, eine schlimm wackelnd, die andere ruhig
    Zwei Kurven, eine schlimm wackelnd, die andere ruhig

    In der Inzidenzkurve fallen wieder Weihnachten und Ostern besonders auf, weil sie selbst in den geglätteten Graphen noch wilde Ausschläge verursachen.

    In der Zeit verschieben

    Schon der optische Eindruck aus dem Rohdaten-Plot legt nahe, die einzelnen Wellen getrennt zu untersuchen, und das ist angesichts von über die Zeit stark veränderlichen Viren, Testverhältnissen, demographischen Gegebenheiten und nichtpharamzeutischen Maßnahmen sicher auch sachlich geboten.

    Deshalb hier zunächst der Verlauf der Ableitungen von Inzidenzen und Intensivbelegung in der aktuellen vierten Welle (das ist eine Kombination der rechten Enden der orangen Graphen der letzten beiden Plots):

    Zwei Kurven, die eine der anderen recht schön folgend

    In dem Graphen steckt noch eine weitere Normalisierung. Und zwar habe ich für beide Arrays etwas wie:

    incs /= sum(abs(incs))
    

    laufen lassen. Damit ist die Fläche zwischen den Kurven …

  • Corona: Neue Filme, Alte Zahlen

    Weil sich sowohl bei Inzidenz als auch bei Altersstruktur der Corona-Meldungen gerade viel tut, habe ich meine beiden Coronafilme neulich neu rechnen lassen. Dabei habe ich beim Inzidenzfilm noch darauf verzeichtet, den Wertebereich über die 350 hinaus zu erweitern, auch wenn das bewirkt, dass sowohl der Kreis mit der höchsten Inzidenz gestern (Miesbach mit 715 Fällen/100'000) als auch der Kreis mit der 33st-höchsten und mithin nur halb so hohen Inzidenz (Ostallgäu mit 350/100'000) saturiert erscheinen.

    Irgendwas werde ich da bei der nächsten Aktualisierung tun müssen, denn, wie z.B. ich im September ausgeführt habe: eine 300-er Inzidenz bedeutet, dass es sechs Jahre dauert, bis alle mal SARS-2 hatten. Es wird im anderen Worten Inzidenzen in den Tausendern brauchen, wenn SARS-2 in nächster Zeit zu einem der anderen humanen Coronaviren werden soll, mit denen zu leben wir alle schon als Kinder schniefend gelernt haben.

    XKCD-cartoon

    Randall Munroe hat mir in der Woche mal wieder aus dem Herzen gesprochen. CC-BY-NC xkcd

    Schniefend, so wie ich jetzt, denn seit letztem Freitag habe ich meine erste richtige Erklältung seit Corona. Ich hatte ganz vergessen, wie doof sowas ist (und nein, ausweislich zweier Antigentests gleich am Freitag und dann am Montag nochmal ist es kein SARS-2). Schon deshalb habe ich gestern mit viel Interesse den Wochenbericht des RKI gelesen, in dem ja immer die Ergebnisse der Influenzasurveillance (Seite 13) berichtet werden. Über die Proben von an respiratorischen Infekten erkrankten Personen steht dort gestern:

    In der virologischen Surveillance der AGI wurden in der 43. KW 2021 in insgesamt 118 von 204 eingesandten Proben (58 %) respiratorische Viren identifiziert. Darunter befanden sich 61 Proben mit Respiratorischen Synzytialviren (RSV) (30 %), 31 mit Rhinoviren (15 %), 20 mit humanen saisonalen Coronaviren (hCoV) (10 %), acht mit SARS-CoV-2 (4 %), sechs mit Parainfluenzaviren (3 %) sowie eine Probe mit humanen Metapneumoviren (0,5 %). Influenzaviren wurden in der 43. KW 2021 nicht nachgewiesen.

    Wenn das irgendwie repräsentativ ist, habe ich eine gute Chance, dass meine derzeitige Pest RSV ist und ich den Rhinoviren Unrecht getan habe, wenn ich sie schon am Freitag mit den saftigsten Flüchen belegt habe. Tatsächlich habe ich aber schon vor dem oben gezeigten XKCD 2535 überlegt, wer mich da wohl gerade quält. Ich glaube jedenfalls, Randall Munroe ist gerade auch erkältet.

    Aber zurück zu meinen Überlegungen vom September: Ich hatte damals ja bejammert, dass wir seit Anfang der Pandemie, von steilen Inzidenzflanken nach oben (etwas niedrigeres int/inc) und unten (deutlich höheres int/inc) abgesehen, eigentlich immer so 20 SARS-belegte Intensivbettern pro Inzidenzpunkt (int/inc) hatten und das grob bedeutet, dass Inzidenzen über 300 ein Gemetzel werden.

    Ich muss leider sagen, dass sich das nicht wesentlich geändert hat. An der stark steigenden Flanke am 2.11.2020 lag int/inc nach RKI-Zahlen bei 2243 ⁄ 120 ≈ 19, derzeit, ebenfalls an einer stark ansteigenden Flanke ist das 2226 ⁄ 155 ≈ 15. Seufz.

    Etwas einschränkend dazu zwei Punkte:

    • Mein Plot neulich hat, wo verfügbar, mit Referenzdaten gerechnet, also, wo rekonstruierbar, den Ansteckungs- und nicht den Meldedaten. Damit kommt mensch für Anfang November 2020 auch auf ein int/inc von rund 15; mit den aktuellen Daten geht das aber nicht, einfach weil von den jetzigen Daten viele Daten aus der Zukunft fehlen, deren Referenzdaten irgendwann mal heute sein werden. Deshalb vergleiche ich hier ganz blind in beiden Fällen die instantanen RKI-Meldezahlen und vergesse meine raffiniertere Technik vom September.
    • Ein wesentlicherer Einwand ist, dass wir in diesem Jahr von einem weitaus höheren Sockel kommen und deshalb in Wirklichkeit die Flanke in der Intensivantwort wesentlich weniger steil ist als im letzten Jahr und sie wahrscheinlich auch in Zukunft vermutlich nicht gleich auf 20 oder sowas zurücklaufen wird, wenn die Inzidenzentwicklung abflacht.

    Das mag so sein, aber qualitativ ändert das alles nicht viel: Unser int/inc ist um mindestens eine Größenordnung zu groß, als dass „wir“ entspannt auf 1000er-Inzidenzen hinlaufen könnten; ob bei 300 (sechs Jahre bis zur Endemisierung) oder bei 500 (vier Jahre) Schluss ist, ist in dieser Betrachtung eher nebensächlich.

    Mein told you so (auch schon im Juli, vierter Absatz) wäre vielleicht befriedigender, wenn das auch im September nicht eigentlich jedeR gesagt hätte, der/die nicht woandershin geschaut hat (was bis neulich sehr populär war, und nur so ist irgendwie plausibel zu machen, warum es ausgereicht jetzt hektische Krisentreffen gibt). Andererseits hatte ich damals auch gesagt:

    wir dürften also, wenn nicht ein Wunder geschieht, in sechs Wochen, Mitte Oktober, deutlich über 4000 liegen und damit in der Gegend der Notbremsenbelastung rund um Neujahr 2021.

    – und damals geschah ein Wunder, denn aus mit dem Sommerreiseverkehr endete auch die damalige exponentielle Füllung der Intensivstationen mit SARS-2-PatientInnen; in DIVI-Zahlen aus den RKI-Tagesberichten:

    Steigende Kurve mit langem Atemholen zwischen Mitte September und Mitte Oktober

    Was zwischen Mitte September und Mitte Oktober – oder, unter der Annahme, dass die Intensivantwort zwei, drei Wochen verzögert auf ihre Ursachen kommt, einfach im September – anders war als davor und danach, das würde mich wirklich interessieren.

  • Von der Gnade, ohne Bild zu sprechen

    Wenn ich „was mit Medien“ machen müsste, würde ich versuchen, irgendwas ohne Video zu erwischen. Und das nicht, weil ich nicht eitel wäre.

    Nein, die Überlegung ist eher verwandt mit meinen Ausführungen zum Panopticon Videokonferenz und werden, wie ich finde, schön illustriert durch einen Vergleich zwischen dem ruhigen, eleganten, fast schon heiteren Stolpern um Mitternacht des 3.7. im Deutschlandfunk und diesem Ausschnitt aus der Tagesschau von gestern; mein Rat wäre, das Video erst mit geschlossenen Augen laufen zu lassen:

    Wie üblich: Wenn euer Browser das nicht abspielt, beschwert euch bei dessen Hersteller. Wer schon überhaupt Videos im Browserfenster anzeigen will, kann zumindest auch webm unterstützen. Die Rechte liegen bei der ARD (also: Das Video ist nicht CC0).

    Gut: Ich habe das natürlich gleich mit all dem Gefummel mit Telefon und Brille wahrgenommen. Ich bin dennoch ziemlich sicher, dass Annette Dittert hier ohne Bild deutlich souveräner rübergekommen wäre.

  • A Local Search Engine for Pelican-based Blogs

    As the number of posts on this blog approaches 100, I figured some sort of search functionality would be in order. And since I'm wary of “free” commercial services and Free network search does not seem to go anywhere[1], the only way to offer that that is both practical and respectful of the digital rights of my readers is to have a local search engine. True, having a search engine running somewhat defeats the purpose of a static blog, except that there's a lot less code necessary for doing a simple search than for running a CMS, and of course you still get to version-control your posts.

    I have to admit that the “less code” argument is a bit relative given that I'm using xapian as a full-text indexer here. But I've long wanted to play with it, and it seems reasonably well-written and well-maintained. I have hence written a little CGI script enabling search over static collections of HTML files, which means in particular pelican blogs. In this post, I'll tell you first a few things about how this is written and then how you'd run it yourself.

    Using Xapian: Indexing

    At its core, xapian is not much more than an inverted index: Essentially, you feed it words (“tokens”), and it will generate a database pointing from each word to the documents that contain it.

    The first thing to understand when using xapian is that it doesn't really have a model of what exactly a document is; the example indexer code, for instance, indexes a text file such that each paragraph is treated as a separate document. All xapian itself cares about is a string („data“, but usually rather metadata) that you associate with a bunch of tokens. This pair receives a numeric id, and that's it.

    There is a higher-level thing called omega built on top of xapian that does identify files with xapian documents and can crawl and index a whole directory tree. It also knows (to some extent) how to pull tokens from a large variety of file types. I've tried it, and I wasn't happy; since pelican creates all those ancillary HTML files for tags, monthly archives, and whatnot, when indexing with omega, you get lots of really spurious matches as soon as people enter a term that's in an article title, and entering a tag or a category will yield almost all the files.

    So, I decided to write my own indexer, also with a view to later extending it to language detection (this blog has articles in German and English, and they eventually should be treated differently). The core is rather plain in Python:

    for dir, children, names in os.walk(document_dir):
      for name in fnmatch.filter(names, "*.html"):
        path = os.path.join(dir, name)
        doc = index_html(indexer, path, document_dir)
    

    That's enough for iterating over all HTML files in a pelican output directory (which document_dir should point to).

    In the code, there's a bit of additional logic in the do_index function. This code enables incremental indexing, i.e., only re-indexing a file if it has changed since the last indexing run (pelican fortunately manages the file timestamps properly).

    Nachtrag (2021-11-13)

    It didn't, actually; see the search engine update post for how to fix that.

    What I had to learn the hard way is that since xapian has no built-in relationship between what it considers a document and an operating system file, I need to explicitly remove the previous document matching a particular file. The function get_indexed_paths produces a suitable data structure for that from an existing database.

    The indexing also defines my document model; as said above, as far as xapian is concerned, a document is just some (typically metadata) string under user control (plus the id and the tokens, obviously). Since I want structured metadata, I need to structure that string, and these days, json is the least involved thing to have structured data in a flat string. That explains the first half of the function that actually indexes one single document, the path of which comes in in f_name:

    def index_html(indexer, f_name, document_dir):
      with open(f_name, encoding="utf-8") as f:
        soup = bs4.BeautifulSoup(f, "lxml")
      doc = xapian.Document()
      meta = {
        "title": soup_to_text(soup.find("title")),
        "path": remove_prefix(f_name, document_dir),
        "mtime": os.path.getmtime(f_name),}
      doc.set_data(json.dumps(meta))
    
      content = soup.find(class_="indexable")
      if not content:
        # only add terms if this isn't some index file or similar
        return doc
      print(f"Adding/updating {meta['path']}")
    
      indexer.set_document(doc)
      indexer.index_text(soup_to_text(content))
    
      return doc
    

    – my metadata thus consists of a title, a path relative to pelican's output directory, and the last modification time of the file.

    The other tricky part in here is that I only index children of the first element with an indexable class in the document. That's the key to keeping out all the tags, archive, and category files that pelican generates. But it means you will have to touch your templates if you want to adopt this to your pelican installation (see below). All other files are entered into the database, too, in order to avoid needlessly re-scanning them, but no tokens are associated with them, and hence they will never match a useful query.

    Nachtrag (2021-11-13)

    When you add the indexable class to your, also declare the language in order to support stemming; this would look like lang="{{ page.lang }} (substituting article for page as appropriate).

    There is a big lacuna here: the recall, i.e., the ratio between the number of documents actually returned for a query and the number of documents that should (in some sense) match, really suffers in both German and English if you don't do stemming, i.e., fail to strip off grammatical suffixes from words.

    Stemming is of course highly language-dependent. Fortunately, pelican's default metadata includes the language. Less fortunately, my templates don't communicate that metadata yet – but that would be quick to fix. The actual problem is that when I stem my documents, I'll also have to stem the incoming queries. Will I stem them for German or for English?

    I'll think about that problem later and for now don't stem at all; if you remember that I don't stem, you can simply append an asterisk to your search term; that's not exactly the same thing, but ought to be good enough in many cases.

    Using xapian: Searching

    Running searches using xapian is relatively straightforward: You open the database, parse the query, get the set of matches and then format the metadata you put in during indexing into links to the matches. In the code, that's in cgi_main; one could do paging here, but I figure spitting out 100 matches will be plenty, and distributing 100 matches on multiple HTML pages is silly (unless you're trying to optimise your access statistics; since I don't take those, that doesn't apply to me).

    The part with the query parser deserves a second look, because xapian supports a fairly rich query language, where I consider the most useful features:

    • Phrase searches ("this is a phrase")
    • Exclusions (-dontmatch)
    • Matches only when two words appear within 10 tokens of each other (matches NEAR appear)
    • Trailing wildcard as in file patterns (trail*)

    That last feature needs to be explicitly enabled, and since I find it somewhat unexpected that keyword arguments are not supported here, and perhaps even that the flag constant sits on the QueryParser object, here's how enabling wildcards in xapian looks in code:

    qp = xapian.QueryParser()
    parsed = qp.parse_query(query, qp.FLAG_WILDCARD)
    

    Deploying this on your Pelican Installation

    You can re-use my search script on your site relatively easily. It's one file, and if you're running an apache or something else that can run CGIs[2], making it run first is close to trivial: Install your equivalents of the Debian python3-xapian, python3-bs4, and python3-lxml packages. Perhaps you also need to explicitly allow CGI execution on your web server. In Debian's apache, that would be a2enmod cgi, elsewhere, you may need to otherwise arrange for mod_cgi or its equivalent to be loaded.

    Then you need to dump blogsearch somewhere in the file system.

    Nachtrag (2022-10-07)

    Don't take it from here; rather, see https://codeberg.org/AnselmF/pelican-ext

    While Debian has a default CGI directory defined, I'd suggest to put blogsearch somewhere next to your blog; I keep everything together in /var/blog (say), have the generated output in /var/blog/generated and would then keep the script in a directory /var/blog/cgi. Assuming this and apache, You'd then have something like:

    DocumentRoot /var/blog/generated
    ScriptAlias /bin /var/blog/cgi
    

    in your configuration, presumably in a VirtualHost definition. In addition, you will have to tell the script where your pelican directory is. It expects that information in the environment variable BLOG_DIR; so, for apache, add:

    SetEnv BLOG_DIR /var/blog/generated
    

    to the VirtualHost.

    After restarting your web server, the script would be ready (with the configuration above …

  • Erstaunlicher Klartext

    Kurve der EEG-Umlage: Aber 2009 gehts steil nach oben.

    Altmaier ist nicht alleine Schuld: Die stark steigenden EEG-Umlagen, die ihm den Vorwand für seinen Feldzug gegen erneuerbare Energien geliefert haben, folgten aus der Ausgleichsmechanismenverordnung (Grafik aus dem Artikel).

    Heute morgen lief im Deutschlandfunk ein recht bemerkenswertes Interview mit Claude Turmes, der für die Grünen in Luxemburg „Energieminister“ ist. Bemerkenswert finde ich dabei nicht seine Einsprüche gegen die ja recht offensichtlich unsinnigen Argumente der VerfechterInnen von Kernenergie – die nur im Zusammenhang mit monströsen Drohungen verstanden werden können, auch wenn, klar, zumindest für anlagenbauende Staaten industriepolitische Aspekte auch eine Rolle spielen.

    Nein, hörenswert ist der erstaunliche Klartext gegenüber einem (noch) wichtigen Mann einer befreundeten Nachbarregierung. Turmes hat bezüglich des Aufbaus einer umweltverträglichen Energieversorgung gesagt (bei Minute 2:30):

    Mit Peter Altmaier – das sind vier verlorene Jahre, und das muss man jetzt denke ich wieder aufholen.

    Nicht, dass dem zu widersprechen wäre, denn Altmaiers Versuche, mit marktwirtschaftlichen „Instrumenten“ die Erzeugung von Wind- und Solarstrom kleinzukriegen, waren wirklich erschreckend effektiv. Dennoch, solche Anwürfe über Staatsgrenzen hinweg (und nicht gegen „die Russen“): Ich bin ein wenig beeindruckt.

    Ich kann einen Post zur Altmaier'schen Klimakatastrophe nicht schließen, ohne darauf hinzuweisen, dass sie in gewissem Sinn Spätfolge einer anderen Mischung aus marktradikaler Verblendung und blanker Gier war. 2010 nämlich hatte eine Koalition aus FDP, den Stromunternehmen und der INSM die Ausgleichsmechanismenverordnung durchgedrückt. Dabei wurde der letztlich ohnehin fiktionale [1] Strom„markt“ weiter „liberalisiert“, mit den erwartbaren Auswirkungen drastisch steigender Kosten und Gewinne – siehe die Grafik oben.

    Das entschuldigt natürlich Altmaier nicht. Er hätte diese Verordnung ja auch kassieren können, statt den Ausbau von Wind- und Sonnenstromerzeugung in Grund und Boden zu bremsen. Aber gerade in Zeiten erneuter FDP-Regierungsbeteiligung schadet es vielleicht nicht, daran zu erinnern, mit welcher Kaltschnäuzigkeit dieses Personal die Welt für ein paar Handvoll Dollar weiter aufgeheizt hat.

    [1]Fiktional ist der Strom„markt“ vor allem, weil da jede Menge Physik und Echtzeit im Spiel sind. In so einem Geschehen müssen sich alle ziemlich eng an Spielregeln halten oder das ganze Netz kippt. Wenn mensch aber schon die Spielregeln vereinbart hat, braucht es eigentlich den Zirkus einer Strombörse nicht mehr. Oder halt nur, damit zwischendrin noch ein paar Leute reich werden können.
  • Whose Streets?

    Foto eines niederländischen Verkehrsschilds: Auto te Gast

    In den Niederlanden nicht überall Utopie: Der Mensch geht dem Auto vor.

    Neulich bin ich bei Dunkelheit über einen (relativ) einsamen Landwirtschaftsweg geradelt. Ein Auto kam mir entgegen und blendete irgendwann für einige Sekunden auf. Verunsichert habe ich nachgesehen, ob ich vielleicht vergessen hatte, mein Licht anzuschalten – aber nein, es war an.

    Als wir uns trafen, raunzte mich der Autofahrer – er hatte immerhin angehalten und sein Fenster heruntergelassen – an, mein Licht habe ihn geblendet. Nun bin ich recht pingelig, was meinen Lichtkegel angeht und bin daher sehr sicher, dass der Fahrer schlicht nicht an LED-Lichter gewöhnt war und er vergleichbar viel Streulicht aus Autoscheinwerfern als ganz natürlich hingenommen hätte.

    Aber das ist hier gar nicht mein Punkt. Mein Punkt ist, dass der Autofahrer zornig abgezischt ist, als ich ihm erklären wollte, wie er zu einem angemesseneren Ton finden könnte. Da das in den üblichen (un-)kommunikativen Situationen im motorisierten Individualverkehr auch sonst aus Zeit-, Geduld- und Hupgründen nicht klappt, will ich kurz eine kleine Fantasie schildern, die vielleicht road rage dieser Art mildern mag. Geheimplan: Die URL dieses Posts könnte ich vielleicht im Straßenverkehr schnell übergeben?

    Nur zu Gast

    Liebe AutofahrerInnen, stellt euch für einen Moment vor, ihr wärt in der Welt nur zu Gast. Wir anderen würden euch und eure tonnenschweren, lärmenden, rasenden und meist noch stinkenden Ungetüme nur aus freundlicher Nachsicht (und nicht wegen staatlicher Gewalt und lebenslanger Prägung) in unseren Städten akzeptieren. Stellt euch vor, ihr müsstet Danke sagen für unsere Bereitschaft, euch all den Platz in unseren Siedlungen einzuräumen, nur damit ihr rasen könnt. Stellt euch vor, ihr könntet nicht mit innerer Überzeugung mit euren parkenden Autos unsere Geh-, Fahr- und Spielfächen verstopfen und müsstet euch bei den Leuten entschuldigen, die den Platz eigentlich gerne nutzen würden.

    Könnt ihr das?

    Ihr sagt, das sei absurde Hippie-Träumerei? Nein, das ist es nicht. Die Auto-Gesellschaft ist nicht alt, und die ersten Autos hatten es nicht leicht auf Straßen, deren traditionelle NutzerInnen nicht immer einsahen, dass sie den Spielzeugen der Reichen auszuweichen hätten. Ohne ordentlich Nachhilfe durch die Vertreter der Reichen in der ersten Hälfte des 20. Jahrhunderts und ganz besonders geschickte Kampagnen der Auto-Industrie (cf. How the car industry outlawed crossing the road bei der BBC) hätte es so bleiben können wie zuvor.

    In Deutschland etwa haben Autos erst seit 1934 Vorrang auf den Straßen (cf. Geschichte der Straßenverkehrssicherheit in der Wikipedia). Noch so ein ärgerliches Erbe des Faschismus. Obwohl, na ja: an einem Gesetz von 1934 ist wohl wahrscheinlich schon vor der Machtübergabe gearbeitet worden. So oder so, die Straßen gehören hierzulande noch nicht mal 100 Jahre lang den Autos. Ob es eine große Kampagne „100 Jahre sind genug“ geben sollte?

    Während ich dann dem Autofahrer aus der Prä-LED-Zeit nachblickte, ist mir die andere Seite der Geschichte recht schlagartig bewusst geworden: Ich wurde von frühester Kindheit an darauf konditioniert, dass die Straße ein verbotener Raum ist, auf dem Menschen nichts verloren haben. Und so fahre ich auch jetzt noch so Fahrrad, als müsse ich mich entschuldigen, da zu sein. Konditioniert von Jahrzehnten wütenden Hupens suche ich Lücken, in denen ich den Autoverkehr möglichst wenig behindere, quetsche ich mich an Straßenränder, um Autos vorbeizulassen, husche ich über Bordsteine, um nur ja nicht „im Weg zu stehen“. Das Motto der Critical Mass, nach dem wir nicht den Verkehr blockeren, sondern wir der Verkehr sind, hatte ich oft im Mund. Aber, so ist mir an dem Abend klar geworden, im Kopf habe ich es nicht so recht.

    Ganz ehrlich: Ein wenig ärgert mich das schon, und ich bin offen neidisch auf die augenscheinlich felsenfeste Überzeugung des_der ideellen Gesamtautofahrenden, die Straße gehöre ihm_ihr.

    Fantasie, gefährlich und schön

    Will ich meine Hasen-Philosophe im Hinblick auf Straßen ändern? Sollte ich einfach auch das Gefühl entwickeln, die Straße gehöre mir?

    Mal ganz abgesehen von generellen Erwägungen zu ethischen Dimensionen von „gehören”: ich fürchte, auf den realen Straßen würden solche Ansprüche schnell im Krankenhaus oder auf dem Friedhof enden; die Macht kommt schon lange nicht mehr nur aus den Gewehrläufen, sie chromblitzt längst auch von Stoßstangen.

    Aber eine schöne Fantasie ist es schon: Die Straßen den Menschen, nicht den Autos. Und da, so ist mein Eindruck, derzeit immer mehr Menschen Träume dieser Art träumen, ist die Rückkehr auf die Straße vielleicht auf Dauer auch keine Gewaltfrage mehr, über die mit Gewehrläufen oder Stoßstangen entschieden wird.

    Vielleicht haben das sogar schon früher mal viele geträumt. Irgendwo glaube ich gelesen zu haben, die französische Volksfrontregierung von 1936/37 habe zumindest in Paris eine grundsätzliche Vorrangregelung für FußgängerInnen beschlossen, und das sei von der folgenden Daladier-Regierung oder den deutschen Nazis zurückgenommen worden. Ich habe dafür im Netz leider keine Belege mehr gefunden. Wenn wer etwas Näheres dazu weiß, wäre ich dankbar für einschlägige Hinweise.

  • Math with ReStructuredText and Pelican

    I recently wrote a piece on estimating my power output from CO₂ measurements (in German) and for the first time in this blog needed to write at least some not entirely trivial math. Well: I was seriously unhappy with the way formulae came out.

    Ugly math of course is very common as soon as you leave the lofty realms of LaTeX. This blog is made with ReStructuredText (RST) in pelican. Now, RST at least supports the math interpreted text role (“inline”) and directive (“block“ or in this case rather “displayed“) out of the box. To my great delight, the input syntax is a subset of LaTeX's, which remains the least cumbersome way to input typeset math into a computer.

    But as I said, once I saw how the formulae came out in the browser, my satifsfaction went away: there was really bad spacing, fractions weren't there, and things were really hard to read.

    In consequence, when writing the post I'm citing above, rather than reading the docutils documentation to research whether the ugly rendering was a bug or a non-feature, I wrote a footnote:

    Sorry für die hässlichen Formeln. Vielleicht schreibe ich mal eine Erweiterung für ReStructuredText, die die ordentlich mit TeX formatiert. Oder zumindest mit MathML. Bis dahin: Danke für euer Verständnis.

    (Sorry for the ugly formulae. Perhaps one of these days I'll write an RST extension that properly formats using TeX. Or at least MathML. Until then: thanks for your understanding.)

    This is while the documentation clearly said, just two lines below the example that was all I had initially bothered to look at:

    For HTML, the math_output configuration setting (or the corresponding --math-output command line option) selects between alternative output formats with different subsets of supported elements.

    Following the link at least would have told me that MathML was already there, saving me some public embarrassment.

    Anyway, when yesterday I thought I might as well have a look at whether someone had already written any of the code I was talking about in the footnote, rather than properly reading the documentation I started operating search engines (shame on me).

    Only when those lead me to various sphinx and pelican extensions and I peeked into their source code I finally ended up at the docutils documentation again. And I noticed that the default math rendering was so ugly just because I didn't bother to include the math.css stylesheet. Oh, the miracles of reading documentation!

    With this, the default math rendering suddenly turns from ”ouch” to “might just do”.

    But since I now had seen that docutils supports MathML, and since I have wanted to have a look at it at various times in the past 20 years, I thought I might as well try it, too. It is fairly straightforward to turn it on; just say:

    [html writers]
    math_output: MathML
    

    in your ~/.docutils (or perhaps via a pelican plugin).

    I have to say I am rather underwhelmed by how my webkit renders it. Here's what the plain docutils stylesheet works out to in my current luakit:

    Screenshot with ok formulae.

    And here's how it looks like via MathML:

    Screenshot with less ok formulae.

    For my tastes, the spacing is quite a bit worse in the MathML case; additionally, the Wikipedia article on MathML mentions that the Internet Explorer never supported it (which perhaps wouldn't bother me too much) and that Chromium withdrew support at some point (what?). Anyway: plain docutils with the proper css is the clear winner here in my book.

    I've not evaluated mathjax, which is another option in docutils math_output and is what pelican's render_math plugin uses. Call me a luddite, but I'll file requiring people to let me execute almost arbitrary code on their box just so they see math into the big folder labelled “insanities of the modern Web”.

    So, I can't really tell whether mathjax would approach TeX's quality, but the other two options clearly lose out against real TeX, which using dvipng would render the example to:

    Screenshot with perfect formulae

    – the spacing is perfect, though of course the inline equation has a terrible break (which is not TeX's fault). It hence might still be worth hacking a pelican extension that collects all formulae, returns placeholder image links for them and then finally does a big dvipng run to create these images. But then this will mean dealing with a lot of files, which I'm not wild about.

    What I'd like to ideally use for the small PNGs we are talking about here would be inline images using the data scheme, as in:

    <img src="data:image/png;base64,AAA..."/>
    

    But since I would need to create the data string when docutils calls my extension function, I in that scheme cannot collect all the math rendering for a single run of LaTeX and dvipng. That in turn would mean either creating a new process for TeX and dvipng each for each piece of math, which really sounds bad, or hacking some wild pipeline involving both, which doesn't sound like a terribly viable proposition either.

    While considering this, I remembered that matplotlib renders quite a bit of TeX math strings, too, and it lets me render them without any fiddling with external executables. So, I whipped up this piece of Python:

    import base64
    import io
    import matplotlib
    from matplotlib import mathtext
    
    matplotlib.rcParams["mathtext.fontset"] = "cm"
    
    def render_math(tex_fragment):
        """returns self-contained HTML for a fragment of TeX (inline) math.
        """
        res = io.BytesIO()
        mathtext.math_to_image(f"${tex_fragment}$",
          res, dpi=100, format="png")
        encoded = base64.b64encode(res.getvalue()).decode("ascii")
        return (f'<img src="data:image/png;base64,{encoded}"'
            f' alt="{tex_fragment}" class="math-png"/>')
    
    if __name__=="__main__":
        print(render_math("\int_0^\infty \sin(x)^2\,dx"))
    

    This prints the HTML with the inline formula, which with the example provided looks like this: \int_0^\infty \sin(x)^2\,dx – ok, there's a bit too much cropping, I'd have to trick in transparency, there's no displayed styles as far as I can tell, and clearly one would have to think hard about CSS rules to make plausible choices for scale and baseline – but in case my current half-satisfaction with docutils' text choices wears off: This is what I will try to use in a docutils extension.

  • Kohlendioxid und die thermische Leistung von Menschen

    Mit meinem zyTemp-Kohlendioxid-Messgerät – das, für das ich neulich Software gebastelt habe – bin ich natürlich gleich in die Welt gezogen, um mal zu sehen, wo es überall CO2 (und mithin plausiblerweise Corona-Aerosol) geben wird.

    Der Wind und die Seuche

    Nachdem mich ja zunächst sehr überrascht hat, wie deutlich sich die Anwesenheit von Menschen in rasch steigendem CO2-Gehalt der Luft in Innenräumen niederschlägt, war meine zweite große Überraschung, dass sich umgekehrt im Freien nichts tut. Also, richtig gar nichts. Selbst Autos, die mindestens eine Größenordnungen mehr CO2 emittieren als Menschen (vgl. unten), fallen schon aus ein paar Metern Entfernung im Wesentlichen nicht mehr auf. Ich musste mich schon an Kreuzungen neben die Ampeln stellen, um überhaupt ein schwaches Signal zu bekommen. Und das war kaum mehr als ein leichtes Oszillieren um ein paar ppm, während die wartenden Autos vor sich hinstanken und dann losbrausten. So sehr es nach Abgasen stank – CO2 ist im Nahbereich von Autos kein Problem.

    Die gute Nachricht ist also: Wenn CO2 ein guter Indikator ist, wie schlimm es mit Aerosol sein kann – real verschwindet Aerosol im Regelfall aus hinreichend ruhiger Luft durch Niederschlag, was CO2 nicht tut – ist praktisch sicher, dass an der frischen Luft bei nicht völlig irren Wetterlagen SARS-2 allenfalls durch Tröpfchen übertragen wird.

    Umgekehrt war meine Sorge ja immer der öffentliche Verkehr, und so habe ich mit Hingabe in verschiedenen Zügen gemessen. Als Referenz: Frischluft liegt derzeit hier irgendwo zwischen 280 und 350 ppm CO2. In einem halb vollen ICE habe ich zwischen 800 und 1400 ppm gemessen (interessanterweise nicht so ganz korreliert mit den Bahnhofsstopps; die Bahn kennend, vermute ich eine Nicht-so-ganz-wie-gedacht-Funktion der Lüftung in dem Wagen, in dem ich saß). Ein vollbesetzter IC-Zug der SBB war zwischen 800 und 1050 dabei, ein leerer Nahverkehrszug bei etwa 400, ein halb voller eher bei 700.

    Bei solchen Dynamiken ist wohl klar, dass hinreichend viel frisches Aerosol in der Luft sein wird, jedenfalls, solange nicht alle Passagiere mit richtig sitzenden FFP2-Masken dahocken, und sowas habe ich noch nicht mal dort gesehen, wo es wie in Berlin und Bayern gesetzlich gefordert ist oder war. Es muss also im letzten Winter weit mehr Ansteckungen in Zügen gegeben haben als das RKI in seinen Ausbruchshistogrammen (z.B. S. 12 am 9.3.2021) mit den kleinen roten Säulen andeutet. Aber ok, sie haben ja immer dazugesagt, „Clustersituationen in anonymen Menschengruppen (z.B. ÖPNV, Kino, Theater)“ seien fast sicher unterrepräsentiert.

    Atmende Blumen

    Aber ich hatte auch anderweitig viel Spaß mit dem Gerät. So war ich neulich verreist, und in der Wohnung verlief die CO2-Konzentration so:

    Graph mit Periodizitäten

    CO2-Konzentrationen in meinem Wohnzimmer während der Abwesenheit aller BewohnerInnen. Zeiten sind in UTC.

    Wohlgemerkt: Da war niemand. Es könnte sein, dass sich hier einfach Schwankungen in der Außenluft reflektieren; aber ich glaube zunächst mal, dass wir hier einer Birkenfeige beim Stoffwechsel zusehen; 6 Uhr UTC, wenn die Kurve sich nach unten wendet, entspricht 8 Uhr Lokalzeit und damit wohl der Zeit, in der es in dem Zimmer hell genug für Photosynthese werden dürfte; der große Peak rund um 18 Uhr am 28.9. wäre schön konsistent damit, dass die Pflanze sich zur Ruhe setzt und dazu kurz mal ihre Mitochondrien anwirft; der folgende Abfall wäre dann wieder ein Mischungseffekt, denn der Sensor lag (mehr zufällig) praktisch in den Zweigen des Ficus. Warum er, das angenommen, den Peak am 29.9. doch deutlich früher gemacht hat? Nun, vielleicht war ja Mistwetter? Oder vielleicht ist das auch alles ganz anders: das bräuchte definitiv mehr Forschung.

    Rauchmelder diagnostizieren Blasenschwäche

    Graph mit einigen Spitzen

    CO2-Konzentrationen in meiner Diele. Zeiten sind in UTC.

    Wenig überraschend zeigt sich, dass die CO2-Konzentrationen dramatisch personenbezogene Daten sind. Der zweite Graph illustriert das an einem relativ harmlosen Beispiel: Der Sensor steht jetzt in der Diele, vor meiner Zimmertür. Deutlich zu sehen ist, dass ich an dem Tag gegen 23 Uhr geschlafen habe, oder jedenfalls, dass meine Schlafzimmertür dann zu war. Und dann bin ich kurz vor zwei mal wach gewesen, weil ich am Abend etwas viel Tee getrunken hatte. am Morgen aufgestanden bin ich um sieben, kurz vor acht habe ich mal gelüftet, und um halb neun bin ich gegangen.

    Wer da etwas länger auf diese Weise zuschaut, findet viel über die BewohnerInnen von Wohungen heraus – angefangen davon, wann wie viele Menschen wo in der Wohnung sind –, und das im Zweifelsfall auch automatisiert unter vielen Menschen. Ich habe dabei lediglich zwei Messwerte pro Minute genommen. Das ginge, so würde ich schätzen, für eine ganze Weile mit den zumindest hier im Haus recht verbreiteten per Funk auslesbaren Rauchmeldern ganz gut, ohne dass ihre Batterien gleich alle wären – für die Betreiber und, weil die Krypto von den Teilen schon aus Stromspargründen sehr wahrscheinlich lausig ist, vermutlich auch ungefähr für jedeN, der/die sich hinreichend intensiv für das Leben der Anderen interessiert.

    Nachdenken nur für 50W?

    Schließlich bin ich jeden Tag wieder aufs Neue fasziniert, wie schnell ich in meinem Büro schlechte Luft verbreite.

    Graph mit zwei recht gleichmäßigen Anstiegen

    CO2-Konzentrationen in meinem Büro; ich komme von der Mittagspause zurück, arbeite, und lüfte ein Mal. Zeiten sind in UTC.

    An dieser Kurve ist viel zu sehen, unter anderem, dass sich offenbar die Luft in dem Raum doch recht schnell mischt; das würde jedenfalls schön erklären, warum es beim Lüften kurz nach 12 Uhr UTC so eine Delle nach unten gibt: Das ist die Frischluft von außen, die ziemlich direkt an den Sensor weht, sich dann aber innerhalb von fünf Minuten mit meinen im Raum gebliebenen Abgasen mischt.

    Diese schnelle Homogenisierung ist wesentlich für eine Überlegung, die sich für mich da aufdrängt: Wie viel CO2 mache ich eigentlich? Das geht so:

    In den 96 Minuten von 10:30 bis 12:06 habe ich die Konzentration von 808 auf 1245 ppm erhöht, was einer Rate von

    ((1245 − 808)  ppm)/((96⋅60)  s) = 0.077  ppm ⁄ s

    entspricht[1] (ich habe das nicht aus dem PNG, sondern noch im Plotprogramm selbst abgelesen). Ein zweiter Datenpunkt ist nach Lüften und Mischen: Da ging es von 12:17 bis 14:08 von 837 auf 1288 ppm, was auf eine Rate von 0.068 ppm/s führt.

    Aus den beiden Werten würde ich grob schätzen, dass ich die CO2-Konzentration in meinem Büro so etwa mit 0.07 ppm/s erhöhe, wenn ich normal arbeite; ich nenne diese Rate hier kurz δ. Unter der sicher falschen, aber vielleicht noch hinnehmbaren Annahme, dass kein CO2 entweicht und der nach den Beobachtungen plausiblen Annahme voller Durchmischung im Raum kann ich nun abschätzen, was mein Stoffwechsel so tut.

    Was es dazu braucht, ist das Wissen, dass bei einem idealen Gas (was die Luft nicht ist, aber für die Abschätzung reicht es) bei „Normalbedingungen“ (die bei mir im Zimmer glücklicherweise auch nicht ganz perfekt realisiert sind) ein Mol 22.4 Liter Volumen einnimmt[2]. Unter Kopfzahl-Aspekten kann ich nicht genau sagen, warum ich mir da drei Stellen gemerkt habe. In Wirklichkeit sind 20 l/mol natürlich genau genug als Kopfzahl. Ich nenne das unten Vm.

    Das ist eine Aussage über die Zahl der Gasmoleküle in einem Volumen V, denn ein Mol sind ungefähr 6e23 (so schreibe ich wieder kurz für 6⋅1023) Teilchen („Avogadro-Konstante“; außerhalb von Kopfzahlen ist die inzwischen exakt festgelegt und definiert das Mol). Mein Büro ist so in etwa fünf Meter lang, 2.5 Meter breit und drei Meter hoch, hat also V = 40 Kubikmeter Rauminhalt. Das heißt, dass sich darin

    (40  m3)/(0.0224  m3 ⁄  mol) ≈ 1800  mol

    befinden. Das sind gegen 1e27 oder 1000000000000000000000000000[3] Moleküle. Diese Zahl hat einen Namen: das ist eine Quadrillarde. Aber klar: der Name ist selbstverständlich Quatsch. Ich musste ihn selbst nachsehen. Der wissenschaftliche Fachbegriff für solche Zahlen ist Gazillion. Für alle davon. Weshalb wir eben immer zehn hoch siebenundzwanzig sagen, was viel nützlicher ist.

    Und dann brauche ich noch die Energie (oder hier genauer die Enthalpie, normalerweise geschrieben als ΔH) die bei der Bildung eines Moleküls CO2 auch C und O₂ frei wird; konventionell geben die Leute das nicht für ein Molekül, sondern für ein ganzes Mol (ihr erinnert euch: ganz platt 6e23 Moleküle statt nur eins) an, und die Wikipedia verrät, dass das 394 kJ/mol sind.

    Jetzt baue ich das zusammen, nämlich die Erzeugungsrate von CO2 in physikalischen Einheiten (statt in ppm/s), δV ⁄ Vm, auf der einen Seite, und mein ΔH auf der anderen Seite. Es ergibt sich für meine Leistung:

    P = ΔHδV ⁄ Vm

    Wenn mensch da die Einheiten glattzieht und bedenkt, dass ppm/s eigentlich 1e-6/s ist, muss mensch was rechnen wie:

    P = 394⋅103  J ⁄ mol⋅0.07⋅10 − 6 ⁄  s⋅40  m3 ⁄ (0.0224  m3 ⁄  mol)

    (ich schreibe das so ausführlich, weil ich mich beim schnellen Hinschreiben prompt verrechnet habe), was rund 50 J/s (also 50 W) ergibt.

    Ich habe von irgendwoher im Kopf, dass ein …

  • Kopfzahlen 2: Wir fressen der Welt die Haare vom Kopf

    Im Hintergrund Politik vom 9.9.2021 habe ich die schockierendste Zahl dieses Jahres gehört. Das Thema wird dort so eröffnet:

    1,6 Milliarden Hektar Ackerflächen gibt es auf der Erde. Nach den Zahlen des Umweltbundesamtes wächst nur etwa auf einem Fünftel davon Nahrung für die menschliche Ernährung. Auf den übrigen vier Fünfteln wird dagegen Tierfutter und Biosprit produziert.

    Auch wenn mir klar war, dass bei der „Wandlung“ von Soja und Getreide in Steaks oder Eier zwischen 70% und 95% des Nährwerts verlorengehen[1] – „Wandlung“ ist hier eingestandenermaßen ein wenig euphemistisch –, hatte ich diese danach relativ naheliegende Konsequenz nicht auf dem Schirm: 80% der Äcker auf diesem Planeten gehen im Groben in „unsere“ (also die der reichen Länder) Völlerei. Derweil:

    Und trotzdem gehen immer noch mehr als 800 Millionen Menschen hungrig zu Bett. Und viele davon sind selbst Kleinbauern – und Kleinbäuerinnen. Sie erwirtschaften auf einem Bruchteil der weltweiten Ackerfläche bis zu 70 Prozent der menschlichen Nahrung, gesicherte Zahlen gibt es hier nicht […]

    Also: unsere tolle, marktgestählte, industrielle Landwirtschaft trägt plausiblerweise nur zu 30% zur Welternährung bei, obwohl sie den Großteil der intensiv zur Nahrungsmittelproduktion nutzbaren Landfläche in Anspruch nimmt (und um die 10% der CO2-Emissionen verursacht). Das war, was mich wirklich schockiert hat.

    Wenn „unsere“ Firmen im globalen Süden für die Produktion unseres Tierfutters und, schlimmer noch, unseres „Biosprits“ Land requirieren und den dortigen Kleinbauern wegnehmen („Landgrabbing“), ist das schlicht ein Hungerprogramm. Wieder mal frage ich mich, wie die Leute in, sagen wir, hundert Jahren auf uns zurückblicken werden.

    Kopfzahlen für große Flächen

    Unterdessen sind die 1.6 Milliarden Hektar von oben ein guter Anlass, meine Kopfzahlen für „große“ Flächen loszuwerden. Erstmal: Ein Hektar ist ein Quadrat, dessen Diagonale ein Mensch in etwa zwei Minuten durchläuft. Nämlich: Ein nicht allzu hektischer Mensch läuft so gegen 4 km/h oder etwas mehr als einen Meter pro Sekunde. Die Diagonale eines Quadrats mit 100 Meter Kantenlänge (eben ein Hektar) sind 100 Meter mal √2 oder rund 150 Meter[2]. Bei einem guten Meter pro Sekunde dauert es also rund 120 Sekunden von Ecke zu Ecke durch die Mitte.

    Ansonsten ist die bessere Einheit für große Flächen der Quadratkilometer, also 100 ha. Mithin: 1.6e9 ha (1.6 ⋅ 109 ist in meinem Eingabeformat ReStructuredText echt lästig zu schreiben, weshalb ich mir hier die in eigentlich allen nichtantiken Programmiersprachen übliche Schreibweise genehmige) sind 1.6e7 oder 16 Millionen km².

    Wie viel ist das? Etwas albernerweise weiß ich immer noch die Fläche der alten Sowjetunion, des in meiner Kindheit größten Staats der Erde: das waren 22 Millionen km². Es gibt also global etwas weniger Acker als das, was mal alles zur Sowjetunion gehörte. Ich denke, der zeitgemäße Ersatz sind einerseits Afrika[3] mit 30 Millionen km² und andererseits die USA und China mit jeweils rund 10 Millionen km². Ich finde, auf diese Weise kriegt mensch schon mal ein Gefühl, wie die global genutzte Ackerfläche auf einer Weltkarte aussähe – und wie viele Wüsten, Tundren, Hochgebirge, Wildnisse oder Parkplätze es so geben wird.

    A propos Welt: Die Erde hat bei einem Radius von etwa 6000 km eine Fläche von 4 π (6000 km)² oder 450 Millionen km² oder auch 15 Mal Afrika, wobei mensch nicht vergessen sollte, dass im Fall der Erde 70% der Fläche von Meeren eingenommen wird und also Afrika deutlich über 20% der Landfläche ausmacht.

    Zwecks Gefühl und Abschätzung merke ich mir noch, dass die BRD eine Fläche von rund 350'000 km² hat (oder, für Leute, die sich das so leichter merken können: eine Drittelmillion). Kombiniert mit der Kopfzahl für Afrika heißt das: die BRD passt 100 Mal in Afrika rein. Von der Fläche her. Vom Ressourcenverbrauch her… eher so zwei Mal[4].

    Die Fläche von Baden-Württemberg ist 35'000 km². Das ist bequem zu merken, weil es einen Faktor zehn kleiner ist als die BRD, und ist als Größenordnung für ernstzunehmende Bundesländer auch sonst ganz brauchbar (die Spanne reicht von 71'000 km² für Bayern bis 16'000 km² für Schleswig-Holstein, also alles grob innerhalb von einem Faktor zwei, was für viele Abschätzungen überallhin reicht).

    So eine Zahl im Kopf ist beispielsweise praktisch, wenn mensch am Neckar steht und abschätzen will, wie viel Wasser da wohl fließt: Sein Einzugsgebiet wird so etwa die Hälfte des Landes sein (fürs einfache Rechnen: 20'000 km²), und es wird da zwischen 500 und 1000 mm pro Jahr regnen (800 mm Niederschläge pro Jahr sind keine unvernünftige Schätzung für normale Gebiete in der BRD). Wegen einfacher Rechnung nehmen wir hier mal 1000 mm, und dann müssten über dem Einzugsgebiet jedes Jahr 20e3 km² ⋅ 1e6 m²/km² ⋅ 1 m an Wasser runterkommen, also irgendwas wie 20e9 m³. Einiges davon wird, gerade im Sommer, verdunsten oder auch von Planzen in Sauerstoff verwandelt, bevor es in Heidelberg vorbeikommt; das Problem verschiebe ich aufs Ende des übernächsten Absatzes.

    20 Milliarden Kubikmeter pro Jahr sind etwas unhandlich, wenn mensch gerade auf den Fluss guckt. Wie sieht es pro Sekunde aus? Nun, ein Jahr hat etwas wie 3e7 Sekunden (vgl. die klassische Kopfzahl in Kopfzahlen 1), also wird der mittlere Abfluss vom Neckar was wie 20/3 ⋅ 10², also rund 670 m³/s sein. Tatsächlich gibt die Wikipedia 145 m³/s für den Pegel Mannheim an.

    Gut: wir liegen einen Faktor vier zu hoch, aber das ist für so eine Abschätzung schon ok, zumal der tatsächliche Abfluss übers Jahr hinweg um weit mehr als so einen Faktor schwanken wird. Wers genauer haben will: das wirkliche Einzugsgebiet sind nur 13'934 km², und dann ist da der Evapotranspirationswert (vgl. Erläuterung in der Wikipedia), dessen Einfluss nicht ganz einfach anzubringen ist, schon, weil einiges des verdunsteten Wassers ja noch im Einzugsgebiet wieder abregnet oder -taut. Egal: Ein Faktor vier ist oft gut genug.

    Letzte Kopfzahlen in dieser Rubrik: Größere europäische Städte wie Berlin oder Hamburg haben so gegen 1000 km² (in Wirklichkeit: 892 bzw. 755), kleinere Städte wie Heidelberg eher so 100 km² (in Wirklichkeit: 109).

    [Alle Flächenangaben hier aus den jeweils erwartbaren Wikipedia-Artikeln]

    [1]Das sind FAO-Schätzungen, die z.B. die gesetzliche Unfallversicherung grafisch schön aufgemacht hat. Die FAO-Quellen habe ich auf die Schnelle nicht im Netz gefunden.
    [2]Eine nicht direkt flächige Kopfzahl obendrauf: √2 ist ungefähr 1.44, und der Kehrwert ungefähr 0.7. Ein wenig flächig ist das aber auch: Die Fläche eines A4-Blatts ist (theoretisch genau) (√2)-(2⋅4) m², für A3 ist sie (√2)-(2⋅3) m² usf. Messt und rechnet selbst oder lest einfach im Wikipedia-Artikel zu Papierformaten nach, warum das so ist.
    [3]Asien (55 Millionen km²) eignet sich nicht so gut als Referenz, weil zu viele Leute Europa als separaten Kontinent abrechnen und zumindest ich mir nie sicher wäre, ob meine Fläche mit oder ohne Europa zählt (die 55 Millionen zählen den ganzen Kontinent, also mit den Gebieten westlich von Ural und kaspischem Meer).
    [4]CO2-Emission der BRD aus Kopfzahlen 1: 2/3 Gigatonnen; für Afrika insgesamt gibt das dort zitierte Our World in Data-CSV 1.4 Gigatonnen.
  • Patriotische Raison

    Die heutige Presseschau im Deutschlandfunk war mal wieder niederschmetternd.

    Zum Afghanistan-„Zapfenstreich“ gestern fällt der taz gerade noch ein, es sei „eher“ ein „Kuriosum“, während die anderen Blätter sich noch fester hinter ihrer Armee versammeln:

    • Es wäre in Afghanistan ja sonst noch viel schlimmer gewesen (Volksstimme); angesichts der tatsächlichen Verhältnisse ist das nur schwer vorstellbar: Geringer als in Afghanistan, 51.3 Jahre laut CIA World Factbook von 2016, ist die Lebenserwartung derzeit nur noch in zwei Staaten.
    • Soldaten – und nicht etwa ihre Opfer – seien „traumatisiert und physisch verletzt“ (Mitteldeutsche; den naheliegenden Schluss, die Bundeswehr, die dafür ja verantwortlich ist, aufzulösen, zieht sie natürlich nicht).
    • „gut gemeint“ (Nürnberger Nachrichten).
    • „Außen- und Sicherheitspolitik den nötigen Raum“ geben (Märkische Oderzeitung).
    Strammstehende und taktstockschwingene Soldaten

    Screenshots vom Zapfenstreich, Rechte fürs Rohmaterial hat die ARD.

    Au weia. Niemand möchte sich durch Stellen der offensichtlichen Frage als vaterlandsloser Geselle outen: „Was war denn das für ein bizarres Spektakel?“

    Strammstehen, Tschingdarassabum, bunte Kappen, Orden? Für Leute, die für die Regierung töten? Leute, es ist 2021. Es hat zwischendurch ein 20. Jahrhundert gegeben. Erinnert ihr euch?

    Den Auweia-Preis für den fürchterlichsten Kommentar räumt aber erneut die FAZ ab, wenn sie fordert:

    Nie wieder darf die ‚Parlamentsarmee‘ in eine Mission geschickt werden, in der sie mit ihrem Blut für die Unschärfe des Mandats und die Inkonsequenz, um nicht zu sagen: Feigheit der politischen Entscheider büßen muss.

    Das ist nur noch ein paar Millimeter von „im Felde ungeschlagen“ und dem Narrativ hinter der Dolchstoßlegende („die tapferen Jungs hätten den Feind schon platt gemacht, aber die Politiker...!“) entfernt. Wenn in dieser Umgebung dann noch Testosteron-Vokabeln wie „feige“ und „Entscheider“ auftauchen, schlägt mein persönliches Jetzt-Flucht-planen-o-Meter schon ziemlich deutlich aus.

  • Ach, Bahn, Teil 1: Captchas und Bodensee

    Alte Waggons und eine Dampflok

    Einen Vorteil hat die dysfunktionale Bahn: Mensch sieht etwas von der Welt, für mich und heute etwa dieses Stück Bahnromantik am Bahnhof von Rottweil.

    Ja, klar, es ist wohlfeil, über die Bahn zu ranten. Andererseits ist es auch unmöglich, mit der Bahn zu reisen und es nicht zu tun. Drum genehmige ich mir das gleich mal in mehreren Teilen. Wobei: Ich bin die Segnungen der Privatisierung nach meinen coronabedingten Zugpausen auch einfach nicht mehr gewöhnt. Vielleicht gibt es gar nicht mehr so viele weitere Teile von „Ach, Bahn“, wenn ich mich erstmal wieder in die Realitäten privatisierter Infrastruktur gefunden habe (vgl. auch Post-Wettbewerb).

    Sprinter und Antisprinter

    Ich fahre gerade von Heidelberg auf die Insel Reichenau. Das sind so etwa 200 Kilometer. Weil ich um 12 Uhr angekommen sein soll, musste ich in Heidelberg um 7:02 losfahren, fünf Stunden vorher. Ok, vielleicht sollen ja Leute eh nicht so viel durch die Gegend fahren, aber wenn ich gestern bei heise lese, die Bahn wolle „mit Inlandsflügen konkurrieren“ und hole künftig zwischen Berlin und Köln eine halbe Stunde raus: Nun, so sehr Flugzeuge viel Dreck und großflächig Lärm machen, das weit größere Problem sind immer noch Autos. In Konkurrenz zu denen werden netto fünf Stunden für 200 km, zumindest solange es Autobahnen gibt, nicht viele Leute auf die Schiene ziehen.

    Oder, in den Worten der taz zu den neuen Sprintern aus dem Heise-Artikel: „Dreimal täglich je Richtung fährt dann zum Beispiel ein Zug ohne Unterbrechung direkt von Berlin nach Köln. Pech gehabt, wenn man in einen Ort dazwischen will.“

    Ja, klar, die komplizierte und langsame Verbindung für mich und heute liegt bestimmt auch daran, dass derzeit die Schwarzwaldbahn nicht fährt. Oh Verzeihung, dass da Schienenersatzverkehr ist. Tja. Ich würde, dies bedenkend, als ein Infrastrukturziel vor dem Ausbau auf 350 km/h-Strecken vorschlagen: Möglichst viele Strecken so ausbauen, dass weitere Bauarbeiten ohne Schienenersatzverkehr und Zugverlegungen durchgeführt werden können. Das würde mir gerade als wichtiger Punkt erscheinen, um die Bahn als Autoalternative glaubhaft zu machen. Zugegeben, vielleicht erscheint mir dieser Punkt wichtiger als er ist, weil ich öfters in der weiteren Umgebung der Stuttgart 21-Katastrophe unterwegs bin. Dort nämlich litt tatsächlich die Mehrheit meiner Zugfahrten in den letzten 10 Jahre darunter, dass Züge, die meine Verbindung viel angenehmer gemacht hätten, baustellenbedingt nicht fuhren.

    Erlaubt mir kurz einen Extra-Nostalgie-Jammer bezüglich der speziellen Heidelberg-Bodensee-Relation: Ich bin alt genug, um mich an den Interregio zu erinnern, der einstmals direkt von Heidelberg nach Konstanz fuhr; ich weiß zwar nicht mehr, wie lang der wirklich brauchte, aber es wäre ja schon mal großartig, wenn ich nicht alle halbe Stunde wieder alles zusammenpacken und umsteigen müsste.

    Ach, Bahn. Sollten die Leute, die damals das Einstampfen der Interregios zu verantworten hatten, schon tot sein, hat der Teufel hoffentlich noch ein paar Grad draufgelegt in dem Teil der Hölle, in den sie gefahren sind.

    Von einem, der mit der Bahn-Webseite interagierte

    Ähnliche Wünsche hege ich im Hinblick auf die Leute, die derzeit an der Bahn-Webseite schrauben. Beim Buchen gestern: Ich logge mich auf bahn.de ein, gebe meine richtigen Credentials ein. Ein Captcha poppt auf. Ich soll nacheinander Fahrräder und Boote markieren. Ich überlege schon, einfach zu Hause zu bleiben, tue der Bahn aber trotzdem den Gefallen. Ich klicke mich zum Bezahlen durch, lasse dort meine gewohnte SEPA-Lastschrift angeklickt.

    Doch irgendwie mag die Bahn das nach nochmaligem Überlegen nicht mehr und schreibt auf der nächsten Seite in Rot: „Die folgenden Zahlungsmöglichkeiten bestehen für diese Reise“ (oder so ähnlich). Lastschrift ist jetzt ohne weitere Erklärung verschwunden. Nach einem tiefen Durchatmen wähle ich Kreditkarte. Ein paar Felder poppen auf, doch kann ich dort nichts eingeben (und, wohlgemerkt, das ist ein hundsordinärer Firefox, nicht mein üblicher Luakit; ich erwarte von der Bahn ja schon gar nicht mehr Browserunabhängigkeit). In einem Versuch, dem Bahn-Code eine zweite Chance zu geben, mache einen Reload. Eine kurze Fehlermeldung, und dann loggt mich der Bahn-Kram aus.

    Ich brülle laut und fluche, knirsche mit den Zähnen und fantasiere von Teufeln an großen Schaltpulten, die die Temperatureinstellung von „fies“ auf „jenseits der Spezifikation“ drehen. Ich wechsele auf einen fast unkonfigurierten, erweiterungslosen Chromium. N-n-n-n-och ein Captcha! Lokomotiven und Lkws dieses Mal. Ich bin jetzt noch heiser von der Fluchkaskade, die sich an dem Punkt meiner Kehle entrang. Immerhin ging vom Chromium aus die SEPA-Lastschrift. Warum auch immer.

    Captchas! C-a-p-t-c-h-a-s!

    For the record: Wenn du wem was verkaufen willst, dann verschwende nicht seine/ihre Zeit mit Captchas. Das ist wirklich ein No-no. Niemand will Objekt verhaltenspsychologischer Experimente sein, und auch nicht von Dingen, die so aussehen. Vielleicht kannst du deine NutzerInnen dazu bringen, da mitzumachen, wenn du so tust, als würdest du ihnen was schenken. Aber wenn sie dir Geld geben, dann musst du im Zweifelsfall Leute bezahlen, um Robots rauszufiltern (wenn es das schon brauchen sollte, was ich in den meisten Fällen bestreiten würde); zur allergrößten Not ist vielleicht noch eine knappe Texteingabe zur Robot-Abwehr („Textcha“) statthaft.

    Völlig absurd ist es natürlich, ein Captcha bei korrekt eingegebenen Credentials zu verlangen. Woher bitte sollten Robots die haben? Was denken sich diese Leute? Wenn das eine Abwehr von Clients sein sollte, die die (eingestanderenmaßen etwas mühsame) Interaktion mit der bahn.de-Webseite automatisieren: Öhm… Was genau wäre das Problem mit denen?

    Oh: Und wenn dich jemand auf so einen solchen Fehler hinweist (das habe ich in diesem Fall schon vor zwei Wochen per Mail an die Bahn-Kontaktadressee gemacht): Reagiere irgendwie. Ein „nur ein wenig Geduld, wir arbeiten an einer Fehlerlösung“ wäre zwar nicht optimal, ist aber immerhin besser als gar nichts. Überflüssig zu erwähnen, dass die Bahn für „gar nichts“ optiert hat.

    Auf der positiven Seite: Die Strecke zwischen Stuttgart und Singen ist hübsch, gerade hier in der Gegend von Horb. Ohne die Schließung der Schwarzwaldbahn hätte ich das wahrscheinlich nie gesehen.

  • Ella: Fast ein Jahr im Gefängnis

    Gerade an dem Tag, an dem in meinen Überlegungen zu Wahlen und Informationstheorie anmerkte, bedeutender als Wahlen sei für die politische Partizipation „eine Justiz, die es häufig genug doch noch schafft, diesen wenigstens dann und wann ein wenig Schutz vor den Übergriffen der Exekutive zu geben“, fand eine verteilte Uraufführung eines Films statt, der Zweifel am „häufig genug“ ziemlich nachhaltig vertieft.

    Es geht darin um den Fall von „Ella“ oder auch „UP1“ für „Unbekannte Person 1“, die seit der brutalen Räumung des Dannenröder Forsts im letzten November im Gefängnis sitzt. Der Fall folgt dem von den Rondenbarg-Prozessen allzu bekannten Muster, bei dem die Polizei lebensgefährliche Einsatzmethoden – im Dannenröder Forst insbesondere das Durchtrennen lebenswichtiger Seile – durch absurd aufgeblasene Vorwürfe gegen die Opfer dieser Einsätze in irgendeinem Sinne zu rechtfertigen versucht; in Ellas Fall kommt sicher noch einiger Zorn über ihre erfolgreiche Personalienverweigerung dazu.

    Einsatzszene

    Ein Sequenz vom Anfang des Ella-Films: Ein Polizist wirft einen Aktivisten von einem Baum runter. Die öffentliche Zurückhaltung angesichts erschreckend gewalttätiger Räumungstechniken im Wald (na gut, inzwischen: Autobahnbaustelle) ist jedenfalls im Hinblick auf künftige Möglichkeiten politischer Partizipation schon ziemlich beunruhigend.

    Die erste Gerichtsinstanz hat dabei mitgemacht, und nun soll sie noch weitere 16 Monate im Gefängnis schmoren. Ohne große Öffentlichkeit wird das wohl auch so kommen, denn ein Landgerichtsprozess geht normalerweise nicht im Eiltempo. Und dann hilft auch ein Freispruch nichts mehr.

    Sowohl im Hinblick auf Ellas Schicksal als auch auf die Diskussion indiskutabler Polizeitaktiken finde ich den Film also höchst verdienstvoll. Wer ihn verbreiten kann, möge das tun, z.B. von youtube; wer das Ding ohne google bekommen will, möge sich per Mail rühren, dann lege ich es auf von mir kontrollierten Webspace (ich spare mir die 800 MB in der Erwartung, dass eh alle zu youtube gehen).

    Nachtrag (2022-05-10)

    Meine Spekulation, es sei bei der ganzen Einsperrerei im Wesentlichen um Ellas Weigerung gegangen, ihre Personalien abzugeben, gewinnt an Substanz. Die Staatsgewalt lässt Ella jetzt, da sie ihre Identität preisgegeben hat, frei, wie die taz berichtet. Die Entscheidung wird den mitredenden Behörden leicht gefallen sein, denn sie werden alle nicht wild sein auf eine weitere Klärung der inzwischen offizell gewordenen Tatsache, dass „die Beamten die Unwahrheit gesagt hatten“ (so die taz sehr staatsfreundlich) und des offensichtlichen Desinteresses beider Gerichtsinstanzen, die Polizei beim Lügen zu erwischen. Zumindest die zweite Instanz kannte ja (vermutlich) den hier besprochenen Film.

  • Wahlen und Informationstheorie

    Ich hatte neulich versprochen, ein paar Worte zu Zweifeln am repräsentativen Modell zu sagen, die sich aus der Informationstheorie speisen. Dazu braucht es zunächst einen Begriff von Information, und um den definieren zu können, ein Modell von Nachrichtenübertragung, in diesem Fall etwa: eine Wahl überträgt die Wünsche zur Organisation der Gesellschaft von Wählenden an die Macht.

    Information: Nachrichten in Bits gemessen

    Wie viel Information steckt nun in den Wunschlisten dieses Modells? Nun, Information – gemessen in Bit – lässt sich recht anschaulich definieren als die Zahl der ja/nein-Fragen, die mensch bei optimaler Fragestrategie im schlimmsten Fall stellen muss, um einer Menge verschiedener Nachrichten eine ganz bestimmte Nachricht rauszufiltern.

    Wenn die Wahl heißt „Parkplätze zu Parks?“ und sonst nichts, reicht eine solche Frage, und mithin wird ein Bit Information übertragen. Kommt als zweite Frage hinzu „Lebkuchen subventionieren?“, braucht es zwei Fragen und mithin Bit, um die kompletten Wünsche zu übertragen.

    Wenn mensch das fortführt, ergibt sich: Für ein komplettes Programm mit n binären Entscheidungen braucht es naiv erstmal n bit Information. Diese n bit reichen aus, um 2n Programme zu kodieren, nämlich alle Kombinationen von ja/nein Entscheidungen über die n Fragen hinweg. Wenn es nur die Parkplätze und die Lebkuchen von oben gäbe, wären das beispielsweise:

    • für Parkplätze/für Lebkuchen
    • für Parkplätze/gegen Lebkuchen
    • gegen Parkplätze/für Lebkuchen
    • gegen Parkplätze/gegen Lebkuchen

    Nochmal: Mit n bits kann ich 2n verschiedene Nachrichten (hier also Programme oder Wunschzettel) auseinanderhalten.

    Das kann mensch jetzt rückwärts aufziehen. Um den Informationsgehalt einer Nachricht herauszubekommen, muss mensch sehen, wie viele verschiedene Nachrichten es gibt und diese Zahl dann als 2x darstellen. Das x in diesem Ausdruck ist der Informationsgehalt in Bit. Das x braucht mensch nicht zu raten, denn es ist nichts anderes als der Logarithmus der Zahl der verschiedenen Nachrichten, genauer der Zweierlogarithmus (meist als ld geschrieben). Wenn euer Taschenrechner den nicht kann: ld x = ln x/ln 2 – aber letztlich kommts nicht so drauf an, denn ln 2 ist nicht viel was anderes als eins. Profis schenken sich sowas.

    Pop Quiz: Wie viele Bits braucht ihr, um eine von 1000 Nachrichten rauszufummeln? (Ungefähr 10). Wie viele, um eine von 1'000'000'0000 zu kriegen? (Ungefähr 30; ihr seht, der Logarithmus wächst sehr langsam).

    Nicht gleichverteilt, nicht unabhängig

    In Wahrheit ist das mit der Information etwas komplizierter. Stellt euch vor, zur Parkplatz-Lebkuchen-Programmatik käme jetzt die Frage „Vorrang für FußgängerInnen auf der Straße?“. Wer die Antwort einer Person auf die Parkplatz-Frage kennt, dürfte recht zuverlässig vorhersagen können, wie ihre Antwort auf die Vorrang-Frage aussehen wird.

    Mathematisch gesprochen sind die beiden Entscheidungen nicht unabhängig, und das führt dazu, dass mensch durch geschicktes Fragen im Schnitt deutlich weniger als drei Fragen brauchen wird, um das komplette Programm mit den drei Antworten rauszukriegen, etwa, indem mensch zusammen nach Parkplätzen und Vorrang fragt. Dieser Schnitt liegt irgendwo zwischen 2 und 3 – für die Mathematik (und den Logarithmus) ist es kein Problem, Fragen auch hinter dem Komma zu zählen: 2.3 bit, vielleicht (ich bin immer wieder erstaunt, wie viele Menschen noch gewillt sind, Parkplätze hinzunehmen, während der Vorrang für FußgängerInnen doch hoffentlich unbestrittener Konsens in der zivilisierten Welt ist[1]).

    Ein ähnlicher Effekt ergibt sich, wenn bestimmte Antworten viel wahrscheinlicher sind als andere. Wenn es z.B. zwei Texte A und B gibt, die jeweils 45% der Nachrichten ausmachen, bekomme ich in 90% der Fälle die Nachricht in nur zwei Fragen raus („Eins von A oder B?“, worauf zu 90% schlicht „A?“ reicht, um die gewählte Nachricht rauszukriegen), ganz egal, ob es noch 10 oder 10'000'000'000 andere Nachrichten gibt.

    Die Sache mit „Information in bit rechnest du als den Logarithmus der Zahl der verschiedenen Nachrichten aus“ gibt also eine Obergrenze für den Informationsgehalt. Sie wird erreicht wenn die Nachrichten gleichverteilt sind (und in gewissem Sinn in sich unabhängig; besser verständlich wird der Unabhängigkeits-Teil, wenn mensch nicht eine Nachricht, sondern eine Folge von Nachrichten betrachtet). Wer wissen will, wie das richtig geht, sei auf die Wikipedia verwiesen.

    Das ganz einfache Modell unabhängiger, gleichverteilter Nachrichten von oben gilt in der Regel nicht – in natürlichsprachigen Texten sind z.B. die Buchstabenhhäufigkeiten drastisch verschieden (Scrabble-SpielerInnen kennen das), und es gibt allerlei Regeln, in welchen Reihenfolgen Buchstaben kommen können. Eine erstaunlich effektive Schätzung für den Informationsgehalt von Nachrichten ist übrigens, einfach mal gzip laufen zu lassen: Für diesen Text bis hierher kommt da 2090 Bytes (á 8 bit) raus, während er auf der Platte 4394 Bytes braucht: Was gzip da geschluckt hat, sind die Abweichungen von Gleichverteilung und Unabhängigkeit, die so ein dummes Computerprogramm leicht finden kann.

    Klar: auch die 2090 ⋅ 8 bit sind höchst fragwürdig als Schätzung für den Informationsgehalt bis hier. Wenn die Nachrichtenmenge „alle bisherigen Blogposts hier“ wäre (davon gibt es etwas weniger als 100), wären es nur sechseinhalb Bit, ist sie „Zeug, das Anselm Flügel schreibt“, wäre es zwar mehr, aber immer noch klar weniger als die 16720 Bit, trotz aller Exkurse über Information und Logarithmen[2]. Informationsgehalt ist nur im Kontext aller anderen möglichen Nachrichten gut definiert. Und dem, was bei EmpfängerInnen ankommt, was bei diesem Post für SchurkInnen auch nur ein Bit sein kann: „Alles Mist“.

    Wie viele bit in einem Wahlzettel?

    Euer Wahlzettel bei der Bundestagswahl neulich dürfte so um die zwei Mal sechzehn Möglichkeiten gehabt haben, etwas anzukreuzen. Im besten Fall – unabhängige Parteien mit gleichen Erfolgschancen – könntet ihr also 8 bit übertragen mit euren zwei Kreuzen. In Wahrheit sorgt schon die 5%-Hürde dafür, dass es allenfalls 8 Listen gibt, die in der Logik repräsentativer Regierungsbildung wählbar sind, und dann noch vielleicht eineN von vier DirektkandidatInnen, die auch nur irgendeine Chance haben. Zusammen, schätze ich (immer noch optimistisch), vielleicht drei Bit.

    Vergleicht das mit den Nachrichten, die so eine Regierung aussendet: So redundant und erwartbar da auch viel sein mag, kein gzip dieser Welt wird die Gesetze, Verordnungen und Exekutivakte von Regierung und Parlament in der letzten Legislaturperiode auf irgendwas unter 100 Megabyte bringen können, selbst wenn es, das Kompressionsprogramm, Politik und Jura schon kann. Gesetze wie das zur Bestandsdatenauskunft etwa sind völlig beliebig: sie setzen einfach Wünsche der Polizeien um und kümmern sich weder um Verfassungen noch um Sinn, und sie würden deutlich anders aussehen, wenn bei BKA, Innenministerium und Polizeiverbänden gerade andere Individuen am Werk gewesen wären. Beliebigkeit ist aber nur ein anderes Wort für Unabhängigkeit und Gleichverteilung. Die 100 Megabyte werden also eine harte untere Grenze sein.

    Bei einem Verhältnis von rund drei Bit rein zu mindestens 100 Megabyte raus (in Worten: eins zu zweihunderfünfzig Millionen, weit unter der Gewinnchance beim 6 aus 49-Lotto) ist evident, dass Wahlen gewiss kein „Hochamt der Demokratie“ sind; ihr Einfluss auf konkrete Entscheidungen wäre auch dann minimal, wenn bei realen Wahlen viel entschieden würde.

    Was natürlich nicht der Fall ist. Niemand erwartet ernsthaft, dass eine Wahl irgendetwas ändert an wesentlichen Politikfragen, hierzulande beispielsweise Reduzierung des Freihandels, Zurückrollen von Privatisierungen, Abschaffung des Militärs, Befreiung der Menschen von der Autoplage, weniger autoritäres Management sozialer Spannungen (z.B. durch weniger übergriffige Polizeigesetze), weniger blutige Staatsgrenzen, weniger marktförmige Verteilung von Boden, kein Wachstum bis zum Kollaps und so weiter und so fort; praktisch die gesamte Bevölkerung hat in allen diesen Punkten die bestehende Regierungspolitik bestätigt, obwohl sie manifest ihren Interessen oder zumindest ihrem moralischen Empfinden widerspricht.

    Warum Wahlen wichtig sind

    Entsprechend tut in den gegenwärtigen Koalitionsverhandlungen nicht mal wer so, als ginge es um mehr als um Selbstverständlichkeiten wie Tempolimits auf Autobahnen (stellt euch mal kurz vor, wie unfassbar bizarr das auf in 100 Jahren eventuell noch lebende Menschen wirken muss).

    Was nicht heißt, dass Wahlen nicht wichtig sind. Die ganz zentrale Funktion von Wahlen dieser Art hat neulich im Deutschlandfunk ein gewisser Andrej Kolesnikow am Beispiel Russland erläutert:

    Die Wahl soll vor allem das Staatsmodell legitimieren, das sich in Russland entwickelt hat. Sie ist deshalb für die Staatsmacht wichtiger als für die Bürger. Die Wahl soll den Menschen auch vor Augen führen, dass die Staatsmacht weiterhin über eine Mehrheit verfügt und dass es besser ist, sich dieser Mehrheit anzuschließen, oder, wenn jemand unzufrieden ist, wenigstens ruhig zu bleiben und seine Unzufriedenheit für sich zu behalten.

    Wer aus ein paar Schritt Entfernung auf die hiesigen Verhältnisse blickt, wird diese Beobachtung auch hierzulande im Wesentlichen bestätigt sehen. Versteht mich nicht falsch: Das ist durchaus wichtig. Ein delegitimierter Staat geht schnell in eine kaputte Gesellschaft über, solange wir es nicht hinbekommen, Menschen auch ohne Nationalgeklingele zu rationalem, sprich kooperativem Verhalten zu bekommen (nicht, dass ich glaube, dass das sehr schwer wäre; es würde aber jedenfalls andere Schulen brauchen). Etwas von dieser Delegitimation sehen wir schon hier, verglichen mit den 1980er Jahren jedenfalls, etwas mehr in den USA, und noch viel mehr im, sagen wir, Libanon. Und etwas weniger als hier in Dänemark oder Schweden. Ich mache kein Geheimnis daraus, wo auf diesem Spektrum ich lieber leben will.

    Allerdings: diese Legitimationsfunktion der Wahl funktioniert weitgehend unabhängig von politischer Partizipation. Auch die finstersten autoritären Regimes halten Wahlen ab und wollen diese in aller Regel auch recht ehrlich …

  • Fortgesetzte Missachtung

    Ich versuche gerade erneut, die Neuregelung (oder eher: Wiederregelung oder vielleicht auch Widerregelung) der Bestandsdatenauskunft nachzuvollzielen und schmökere dazu im letzten einschlägigen Urteil des Bundesverfassungsgerichts vom 27.5.2020 (1 BvR 1873/13). Darin findet das Gericht zunächst, dass die Anfechtungen von Regelungen im BND-Gesetz und im Zollfahndungsgesetz wegen Verfristung unzulässig sind, da der Bundestag in der Zwischenzeit bereits wieder Verschärfungen der entsprechenden Gesetze abgenickt hatte (Rn. 66ff).

    Das ist schon mal sportlich: Die Regierung beschließt neuen Menschenrechtsabbau, bevor das Verfassungsgericht den alten beanstanden kann. Das ist nicht die ungeschickteste Art, auf die Beschränkungen zu reagieren, die so eine lästige Verfassung mit sich bringt.

    Noch bemerkenswerter finde ich allerdings folgende, für höchstreichterliche Verhältnisse doch sehr klaren Worte des Gerichts (Rn. 80):

    Auch § 113 Abs. 1 Satz 2 TKG konnte fristgerecht angegriffen werden. Zwar hat die Norm gegenüber der Vorgängerregelung vom 22. Juni 2004 (BGBl I S. 1190) ‒ trotz geänderten Wortlauts und neuer Regelungsstruktur ‒ für sich genommen keinen grundsätzlich neuen Gehalt. Die Vorgängerregelung wurde jedoch für verfassungswidrig erklärt (BVerfGE 130, 151). Wenn der Gesetzgeber nunmehr eine Regelung mit im Wesentlichen gleichem Inhalt wiederholt, stellt diese einen neuen verfassungsrechtlichen Prüfungsgegenstand dar (vgl. dazu BVerfGE 96, 260 <263>; 102, 127 <141>; vgl. auch BVerfGE 135, 259 <281 Rn. 36>).

    Mit anderen Worten: Das Gericht hat den alten 113er für menschrechtswidrig erklärt, woraufhin die Regierung das Ding einfach ein wenig umformuliert und ganz offenbar im Wissen um seine Menschenrechtswidrigkeit völlig unverfroren wieder beschließen lässt. Dass keineR der betroffenen ParlamentarierInnen den Mut hatte, Einspruch gegen diesen doch besonders offensichtlichen Fall von Grundrechtsfeindlichkeit einzulegen, könnte in Hinblick auf die Funktionsfähigkeit des Parlaments ernüchtern. Andererseits ist das Muster autoritärer Problembewältigung (kein Mitglied der Regierungsparteien darf öffentlich gegen Wünsche von Polizei und Militär sprechen; privat tun sie das übrigens durchaus) leider allzu vertraut, und zwar von allen deutschen Regierungen zumindest in diesem Jahrtausend.

    Wobei: Immerhin hat die Regierung nicht (erkennbar) die vom BVerfG beanstanden Passagen noch wesentlich weiter getrieben (die Passwortabfrage ist wohl eher ein sachlicher Unkenntnis geschuldetes Gimmick). So ein Weitertreiben gab es durchaus schon, vielleicht am eklatantesten bei den Terrordateien (ATD und RED), deren Verschärfungen von 2015 vorgaben, durch recht fundamentale Beanstandungen des BVerfG motiviert zu sein, aber in Wirklichkeit die Menschenrechtsverstöße noch zuspitzten. Oder, wie Michael Plöse richtig feststellt (Vorgänge 208, 4/2014):

    [Der Regierungsentwurf zum ATDG-ÄG kann] kaum mehr nur als eine Enttäuschung bezeichnet werden – er ist vielmehr eine dreiste Provokation des Karlsruher Verfassungskompromisses.
  • Foced https Redirects Considered Harmful

    I don't remember where I first saw the admontion that “not everything that does HTTP is a browser“ – but I'd like to underscore this here. One corollary to this is:

    Please do not unconditionally redirect to https!

    People may have good reasons to choose unencrypted http, and sometimes they don't get to choose, in particular in embedded systems (where https may be prohibitively large) or when you cannot upgrade the ssl libraries and sooner or later the server no longer considers any of the ciphers you know safe.

    Case in point: I have a command line program to query bahn.de (python3 version)…

    Nachtrag (2022-09-04)

    after many years of relative stability, the Bahn web page has significantly changed their markup, which broke this script. There is a new bahnconn now.

    …which screen-scrapes the HTML pages that Deutsche Bahn's connection service hands out. I know bahn.de has a proper API, too, and I'm sure it would be a lot faster if I used it, but alas, my experiments with it were unpromising, with what's on the web working much better; perhaps I'll try again next time they change their HTML. But that's beside the point here.

    The point is: In contrast to browsers capable of rendering bahn.de's HTML/javascript combo, this script runs on weak hardware like my Nokia N900. Unfortunately, the N900 is more or less frozen at the state of something like Debian Lenny, because its kernel has proprietary components that (or so I think) deal with actually doing phone calls, and hence I can't upgrade it beyond 2.6.29. And that means more or less (sure, I could start building a lot of that stuff from source, but eventually the libc is too old, and newer libcs require at least kernel 2.6.32) that I'm stuck with Python 2.5 and an OpenSSL of that time. Since about a year ago, these have no ciphers any more that the bahn.de server accepts. But it redirects me to https nevertheless, and hence the whole thing breaks. For no good reason at all.

    You see, encryption buys me nothing when querying train connections. The main privacy breach here is bahn.de storing the request, and there I'm far better off with my script, as that (at least if more people used it) is a lot more anonymous than my browser with all the cookies I let Deutsche Bahn put into it and all the javascript goo they feed it. I furthermore see zero risk in letting random people snoop my train routes individually and now and then. The state can, regrettably, ask Deutsche Bahn directly ever since the Ottokatalog of about 2002. There is less than zero risk of someone manipulating the bahn.de responses to get me on the wrong trains.

    Now, I admit that when lots of people do lots of queries in the presence of adversarial internet service providers and other wire goblins, this whole reasoning will work out differently, and so it's probably a good idea to nudge unsuspecting muggles towards https. Well: That's easy to do without breaking things for wizards wishing to do http.

    Doing it right

    The mechanism for that is the upgrade-insecure-requests header that essentially all muggle browsers now send (don't confuse it with the upgrade-insecure-requests CSP). This does not lock out old clients while still giving muggles some basic semblance of crypto.

    And it's not hard to do, either. In Apache, you add:

    <If "%{req:Upgrade-Insecure-Requests} == '1'">
      Header always set Vary Upgrade-Insecure-Requests
      Redirect 307 "/" "https://<your domain>/"
    </If>
    

    rather than the unconditional redirect you'd otherwise have; I suppose you can parameterise this rule so you don't even have to edit in your domain, but since I'm migrating towards nginx on my servers, I'm too lazy to figure out how. Oh, and you may need to enable mod_headers; on Debian, that would be a2enmod headers.

    In nginx, you can have something like:

    set $do_http_upgrade "$https$http_upgrade_insecure_requests";
    location / {
    
      (whatever you otherwise configure)
    
      if ($do_http_upgrade = "1") {
         add_header Vary Upgrade-Insecure-Requests;
         return 307 https://$host$request_uri;
      }
    }
    

    in your server block. The trick with the intermediate do_http_upgrade variable makes sure we don't redirect if we already are on https; browsers shouldn't send the header on https connections, but I've seen redirect loops without this trick (origin).

    Browser considerations

    Me, I am by now taking it as a sign of quality if a server doesn't force https redirects and instead honours upgrade-insecure-requests. For instance, that way I can watch what some server speaks with the Javascript it executes on my machine without major hassle, and that's something that gives me a lot of peace of mind (but of course it's rather rare these days). In celebration of servers doing it right, I've configured my browser – luakit – to not send upgrade-insecure-requests; where I consider https a benefit rather than a liability for my privacy, I can remember switching to it myself, thank you.

    The way to do that is to drop a file no_https_upgrade_wm.lua into .config/luakit containing:

    local _M = {}
    
    luakit.add_signal("page-created",
        function(page)
            page:add_signal("send-request", function(p, _, headers)
                if headers["Upgrade-Insecure-Requests"] then
                    headers["Upgrade-Insecure-Requests"] = nil
                end
            end)
    end)
    

    (or fetch the file here). And then, in your rc.lua, write something like:

    require_web_module("no_https_upgrade_wm")
    

    ...and for bone-headed websites?

    In today's internet, it's quite likely that a given server will stink. As a matter of fact, since 1995, the part of the internet that stinks has consistently grown 20 percentage points[1] faster than the part that doesn't stink, which means that by now, essentially the entire internet stinks even though there's much more great stuff in it than there was in 1995: that's the miracle of exponential growth.

    But at least for escaping forced https redirects, there is a simple fix in that you can always run a reverse proxy to enable http on https-only services. I'm not 100% sure just how legal that is, but as long as you simply hand through traffic and it's not some page where cleartext on the wire can realistically hurt worse than the cleartext on the server side, I'd claim you're ethically in the green. So, to make the Deutsche Bahn connection finder work with python 2.5, all that was necessary was a suitable host name, an nginx, and a config file like this:

    server {
      listen 80;
      server_name bahnauskunft.tfiu.de;
    
      location / {
        proxy_pass https://reiseauskunft.bahn.de;
        proxy_set_header Host $host;
      }
    }
    
    [1]This figure is of course entirely made up<ESC>3bC only a conservative guess.

« Seite 13 / 17 »

Letzte Ergänzungen