Jeff Epler's blog

6 October 2024, 19:32 UTC

Enabling markdown


This blog is based on its own weird markup language.

Now in 2024 everyone's used to markdown instead.

So I made a markdown tag for this blog.

You won't notice much as a reader, but for me this is a big convenience.

One thing I noticed is that code rendered inside backticks looks a bit
different.

[permalink]

6 October 2024, 14:15 UTC

Talking directly to in-process tcl/tk


I recently saw a post on a blog about using wish as a subprocess of Python, as a way to access tk without the complexity of tkinter.

To be clear the original post also calls out the situation where the tkinter part of python is not installed by default, and my technique would not be applicable there.

So what do you do if you like Tk but don't care for the high level abstraction provided by Tkinter? Well, you can import _tkinter and create a tkapp object with _tkinter.create().

The _tkinter module and the tkapp object is largely undocumented, but in Python 3.11 here are some useful methods:

  • tkapp.createcommand: Create a callback into Python code. Takes a string and a callable. Creates a Tcl command with that name, that calls back into Python code: app.createcomand("cb", lambda *args: print("cb", args))
  • tkapp.eval: Takes a command and evaluates it. Call it with a single string: app.eval("button .b -text HI -command {cb arg1 arg2}")
  • tkapp.call: Takes a series of arguments, does proper Tcl quoting, and evaluates it: app.call("pack", ".b")
Now, go forth and program your graphical app without all that Python object nonsense!

Python 3.11.2 (main, Aug 26 2024, 07:20:54) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import _tkinter
>>> app = _tkinter.create()
>>> app.createcommand("cb", lambda *args: print("cb", args))
>>> app.eval("button .b -text HI -command {cb arg1 arg2}")
'.b'
>>> app.call("pack", ".b")
''
>>> # Now I click the button several times ...
>>> cb ('arg1', 'arg2')
cb ('arg1', 'arg2')
cb ('arg1', 'arg2')

[permalink]

26 July 2024, 0:17 UTC

Vintage X Bitmap Font "VT100 graphics characters"


In an old "5x9" bitmap font a friend gave me I was puzzled about the chacters in positions 1..31, normally used as control characters.

It turns out these are the "VT100 graphics characters", and the xfonts-utils program ucs2any can optionally put specific Unicode code points in these spots:

/* DEC VT100 graphics characters in the range 1-31 (as expected by some old xterm versions and a few other applications) */
#define decmap_size 31
static int decmap[decmap_size] = {
        0x25C6, /* BLACK DIAMOND */
        0x2592, /* MEDIUM SHADE */
        0x2409, /* SYMBOL FOR HORIZONTAL TABULATION */
        0x240C, /* SYMBOL FOR FORM FEED */
        0x240D, /* SYMBOL FOR CARRIAGE RETURN */
        0x240A, /* SYMBOL FOR LINE FEED */
        0x00B0, /* DEGREE SIGN */
        0x00B1, /* PLUS-MINUS SIGN */
        0x2424, /* SYMBOL FOR NEWLINE */
        0x240B, /* SYMBOL FOR VERTICAL TABULATION */
        0x2518, /* BOX DRAWINGS LIGHT UP AND LEFT */
        0x2510, /* BOX DRAWINGS LIGHT DOWN AND LEFT */
        0x250C, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */
        0x2514, /* BOX DRAWINGS LIGHT UP AND RIGHT */
        0x253C, /* BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
        0x23BA, /* HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */
        0x23BB, /* HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */
        0x2500, /* BOX DRAWINGS LIGHT HORIZONTAL */
        0x23BC, /* HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */
        0x23BD, /* HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */
        0x251C, /* BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
        0x2524, /* BOX DRAWINGS LIGHT VERTICAL AND LEFT */
        0x2534, /* BOX DRAWINGS LIGHT UP AND HORIZONTAL */
        0x252C, /* BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
        0x2502, /* BOX DRAWINGS LIGHT VERTICAL */
        0x2264, /* LESS-THAN OR EQUAL TO */
        0x2265, /* GREATER-THAN OR EQUAL TO */
        0x03C0, /* GREEK SMALL LETTER PI */
        0x2260, /* NOT EQUAL TO */
        0x00A3, /* POUND SIGN */
        0x00B7  /* MIDDLE DOT */

Apparently on a real VT100 these are printed by sending various escape codes that cause them to be mapped at character values 95..126. various escape sequences.

[permalink]

9 July 2024, 2:33 UTC

datetime in local time zone


ruff and flake8 consider that it is a mistake to ever use "naive" datetime objects.

However, this leaves no "obvious" way to do something like convert between local and UTC time using the datetime module!

I was not able to find a way in standard Python, but I was able to find it in dateutil (based on some stackoverflow where the top answer was a wrong answer, purposely not linked)

So: Use dateutil.tz.gettz() to get a timezone object that reflects the system's local time zone. This will pass ruff's checks.

>>> localtime = dateutil.tz.gettz()
>>> localnow
datetime.datetime(2024, 7, 8, 21, 33, 51, 897086, tzinfo=tzfile('/etc/localtime'))
>>> localnow.astimezone(datetime.timezone.utc)
datetime.datetime(2024, 7, 9, 2, 33, 51, 897086, tzinfo=datetime.timezone.utc)

You can also use a piece of code from the documentation called LocalTimeZone that makes a best effort to implement the local system time zone based on the items available in the "time" module. This has caveats, such as not working properly when a zone historically had different offsets than it does today, and you can't pip install it, you have to copy & paste it.

[permalink]

1 July 2024, 18:44 UTC

Sunflower Butter aka Sun Butter or Sunbutter (plus Chocolate Sun Butter)


These two bread toppings are part of our regular breakfasts and we hate to be without them.

Recipe makes two pint jars: one of regular sun butter and one of chocolate sun butter.

If you're used to nutella, this chocolate sun butter will be a surprise (but a good one, in my opinion)! It has much less sugar. If you like a sweeter product, you can use milk chocolate chips, or add additional refined sugar.

  • 3 quarts raw unsalted shelled sunflower seeds
  • olive oil as needed (around 2 tablespoons?)
  • salt according to taste (from a pinch to a teaspoon/5 grams)
  • generous 1/2 cup dark chocolate chips
  • up to 1/2 cup cocoa nibs for chunky chocolate sun butter

Special equipment needed:

  • "Half sheet" rimmed baking tray (US: 13x18 inches; I don't know how these are called in the rest of the world but that's about 330x450mm)
  • Food processor with 6 cup (1.5l) capacity

Directions:

  1. Preheat oven to 350°F (175°C)
  2. Place sunflower seeds on tray and place in oven
  3. Bake for a total of 25-30 minutes according to preference. Stir after 15 minutes
  4. Allow to cool for 10 minutes, then scoop all seeds into food processsor (or reserve 1/4 cup if you want a chunky sun butter)
  5. Allow to process in the food processor, regularly stopping to scoop the insides and bottom. At first this will be very powdery or grainy.
  6. When you feel you aren't making any progress, add olive oil in small increments and continue processing. At some point the mass will turn clumpy and finally become almost like a liquid.
  7. Process until the desired consistency is achieved. Taste for salt, oil, and texture; but be aware that the food processor has heated the mixture.
  8. If making chunky sun butter, add the reserved seeds and process briefly
  9. Scoop out a pint (470ml) of the paste: this is your finished sun butter.
  10. Add the chocolate chips to the food processor. It should be warm enough to melt the chips. Process long enough to thoroughly mix, scooping the inside at least once to ensure complete mixing.
  11. If making chunky chocolate sun butter, add the reserved seeds and/or cocoa nibs and process briefly
  12. Scoop out the remainder. This is your finished chocolate sun butter.

To make just a quart of sun butter, use 2 quarts sunflower seeds.

My food processor doesn't do a great job with just 1 quart of sunflower seeds, so if you want just the chocolate version I recommend using 2 quarts sunflower seeds & double the chocolate ingredeients.

We keep this in the cupboard and eat a batch over the course of a few weeks. It will also refrigerate for longer storage, but it's best to eat at room temperature.

[permalink]

2 June 2024, 12:31 UTC

An efficient pair of polynomials for approximating sincos


In CicuitPython, I was looking for an efficient polynomial approximation of sin(x) and cos(x) over the interval [0:π/2].

Being a simple person, I searched enough documentation to find that numpy was happy to make a polynomial approximation of any table of data:

>>> x = np.linspace(0, np.pi/2, 10000)
>>> Polynomial.fit(x, np.cos(x), deg=5)
Polynomial([ 0.70710188, -0.55535829, -0.21798633,  0.05707696,  0.01090002,
       -0.00171961], domain=[0.        , 1.57079633], window=[-1.,  1.], symbol='x')
>>> Polynomial.fit(x, np.sin(x), deg=5)
Polynomial([ 0.70710188,  0.55535829, -0.21798633, -0.05707696,  0.01090002,
        0.00171961], domain=[0.        , 1.57079633], window=[-1.,  1.], symbol='x')

But wait, the coefficients are the same, except for some of the signs? What's going on? That's not how sin and cos are related.

The design of numpy's polynomial fit routine has given us an unexpected gift: the original domain of 0 to π/2 has been changed (offset & scaled) to the window -1 to 1. This happens to make the shifted-sin and shifted-cos routines reflect around the line x=0. So it's no surprise that the even coefficients are the same (as (-x)^2k = x^2k for integers k) and the odd coefficients are negated.

This leads to an implementation of sincos that only has to do fewer multiplications than I expected, and will therefore be a bit quicker.

Oh, and the maximum absolute error for this polynomial seems to be about 5e-6, or little enough that it probably can't be noticed when processing 16-bit audio.

C implementation of the algorithm:

Compiled on godbolt with -Os -mcpu=cortex-m4 -mhard-float -fsingle-precision-constant, it gives some very tidy-looking code.

fast_offset_scaled_sincos(float, sincos_result*):
  vmul.f32 s15, s0, s0
  vldr.32 s12, .L2
  vmul.f32 s14, s15, s15
  vmul.f32 s13, s0, s15
  vmul.f32 s14, s14, s12
  vldr.32 s12, .L2+4
  vfma.f32 s14, s15, s12
  vldr.32 s12, .L2+8
  vmul.f32 s15, s15, s13
  vadd.f32 s14, s14, s12
  vldr.32 s12, .L2+12
  vmul.f32 s15, s15, s12
  vldr.32 s12, .L2+16
  vfma.f32 s15, s13, s12
  vldr.32 s13, .L2+20
  vfma.f32 s15, s0, s13
  vadd.f32 s13, s14, s15
  vsub.f32 s14, s14, s15
; function epilogue and table of constants (L2) omitted

Note: More optimal coefficients can come from better algorithms like Remez, as implemented by py-remezfit. The resulting worst error is a bit higher but the average error should be lower.

$ python3 remezfit.py -d single  -- "lambda x: np.cos((x+1)*np.pi/4)" -1 1 5
p = array([ 0.7071076   , -0.5553769   , -0.21797441  ,  0.05672453  ,
        0.011838001 , -0.0023185865], dtype=float32)
        
prec = 7.189810e-06

[permalink]

31 May 2024, 2:04 UTC

Leaving my roles in LinuxCNC


It's been a long time since I actively participated in this project and I want to let you all know that I have begun to remove myself from roles in various places including sourceforge & github. I'm in private discussions to ensure this happens without disrupting the project.

To the developers & community: Thank you so much for letting me play a part in this project. It was an important chapter of my life (that started around 20 years ago!) and I wish you all the best for the future. I hope and trust you'll continue to take this project in good directions. I wish you all the best.

[permalink]

13 January 2024, 15:04 UTC

Linux ThinkPad T16 Microphone "Muted" Indicator


This laptop's keyboard has an indicator on the F4 key, which also serves as the mic mute toggle key.

Frustratingly, in Debian 12 ("bookworm") with pipewire audio, the LED doesn't actually follow the mic mute state. This appears to be because pipewire doesn't mute the mic at the hardware level, so setting the corresponding LED's "trigger" to "audio-micmute" does nothing.

I don't know what the proper solution for this is, but I implemented a solution of my own and it seems to work.

First, the LED control file (in my case "/sys/devices/platform/thinkpad_acpi/leds/platform::micmute/brightness") has to be made writable by my user (I don't care about multi-user situations). I did this by making a boot-time cron job as root:

@reboot chown jepler.jepler  /sys/devices/platform/thinkpad_acpi/leds/platform::micmute/brightness

Second, I have to run a script that watches the PulseAudio mic mute status and updates the LED. It's shown at the bottom of this blog post. It requires python3 and the pulsectl library, installable via pip.

I start this script in the background at login time. In my case I do this via the ".xsession" script, but you will need to know the correct way to do it in your desktop environment.

Now, when I press the mute key, Fn-F4, the LED's state follows the mute state.

read more…

7 July 2023, 13:49 UTC

"Letter Boxed" puzzle statistics

6 July 2023, 19:04 UTC

NYT "Letter Boxed" solver in C++

8 June 2023, 22:51 UTC

Conservation of Experience

11 May 2023, 12:27 UTC

Xerox 820 & CP/M

27 March 2023, 0:01 UTC

Welcome to the Polity

All older entries
Website Copyright © 2004-2024 Jeff Epler