Jeff Epler's blog

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)
>>>, 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')
>>>, 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 -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


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.


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…

15 November 2023, 20:46 UTC

Notes on using skyui with Skyrim Anniversary Edition from GOG

1. install Lutris
2. enter GOG credentials
3. Install Elder Scrolls Special Edition
4. Install DLC
5. Run skyrim once and quit
6. manually download:
  - vortex mod from nexusmods
  - skse64 from
  - skyui from
  - unofficial patch from nexusmods
7. In lutris, "run exe inside wine prefix", select vortex mod exe
   (uncheck the "run vortex" option in the last installer step)
8. In lutris, duplicate the skyrim launcher. Call the duplicate "vortex (skyrim se) and set its executable to .../Program Files/Black Tree Gaming Ltd/Vortex/Vortex.exe. Open the new program, and select the first option when prompted
9. On first run, agree to downloading microsoft .net
10. find "skyrim special edition" under unmanged (use "search for a game")
    and then select its location under My Computer / Drive C / GOG
11. Click to the mods pane.
12. drag & drop the downloaded skse64 and skyui 7z files to vortex
13. change the skyrim launcher path to .../GOG Games/Skyrim Anniversary Edition/skse64_loader.exe
14. Launch skyrim, choosing the first option


7 July 2023, 13:49 UTC

"Letter Boxed" puzzle statistics

You might wonder, for a fully random "letter boxed" puzzle, what proportion are unsolveable?

I modified my solver from the previous post to also generate random puzzles, and analyzed the results of 100,000 runs. For these runs, I used a dictionary from github that claimed to be "the scrabble dictionary", though I have no way of verifying it. It contained 178691 words which I had to convert to lowercase due to the way my search program is implemented.

To the statistics:

  • About 37% of puzzles did not result in a solution of up to 12 words
  • The most frequent number of letters in a solution was 18; the least was 13 and the most was 38
  • The median frequent number of letters in a solution was 19
  • The most frequent and median number of words in a solution was 4; the least was 2 and the most was 10
  • About 5% of puzzles with a solution had a 2-word solution; about 30% had a 3-word solution.

I haven't played enough official puzzles to know, but I suspect that there are additional constraints on the letters (For example, no Q without U; vowels on at least 3 edges), or puzzles are constrained by having at least one "relatively short" answer, or the puzzle is fully human curated.

Here's the worst puzzle I found, using that dictionary from above. It has a shortest solution of 10 words & 39 characters: fysvzhcplgim

puzzle answer for fys / vzh / cpl / gim ghi - ich - hip - phiz - zips - shiv - vivific - chili - ism - myc

Not all of those seem like words to me, but that is how scrabble dictionaries are.


6 July 2023, 19:04 UTC

NYT "Letter Boxed" solver in C++

My wife and I regularly solve the NYT crossword in the app.

Lately it's been trying to get me to solve a puzzle called "Letter Boxed".

In this puzzle, there are 12 letters arranged with 3 on each side of a square. Valid words have to be made from the letters, with the additional constraint that two consecutive letters may not be on the same side of the square. For example if the edges are "xoy", "tws", "apv" and "kri" then "air" is not a valid word because the consecutive letters "ir" come from the same side. "vow" is. The last constraint is that each consecutive word starts with the last letter of the previous word, so "vow" can be followed by "wok" but not "sat". (Words of any length are permitted, not just 3 letters)

There is no particular scoring to the game, though you're suggested to "try to solve it in # words"; you can also view the previous day's suggested solution.

To me, it seems that the best answer is in the fewest words, with ties broken by the fewest number of characters.

The following program (which assumes that there's a standard unix-style dictionary at /usr/share/dict/words) can find what appear to be "optimal" solutions in only a few milliseconds. Simply supply it with the 12 letters as the first commandline argument, and it will perform a breadth-first search (BFS) with up to 5 words in length. Each candidate printed is better scoring (shorter) than the previous one, so the last line is the best score.

$ ./a.out xoytwsavpkri
228 candidate words
proviso - oaks - sixty - yaw
provisos - sixty - yaw - wk
vow - warps - sixty - yak
Checked 3027570 sequences

I originally wrote a program in Python, but memory usage was high and speed was low. This version uses an efficient structure where each word is reduced to a 32-bit quantity that tracks the letters present, the word length, and the word's terminal character. A particular game play is characterized by a fixed-size data structure that includes the characters used so far, the number of words, the number of total characters, and an array of up to 5 words. The deque is initialized with one entry for each possible word. In the main loop, the item is taken from the front of the deque. Then, based on the terminal letter of the last word played it tries each word starting with that letter. If this potential solution is not lower-scoring than the best one, then it stops evaluating. Otherwise, if this word completes the puzzle, the solution is printed and the best known score values are updated. Otherwise, this puzzle state is added to the end of the deque. The program loops until all possibilities have been evaluated.

A dynamic-programming approach would probably beat the BFS but BFS is quite fast enough for the published puzzles I've solved. It might also be possible to work from both ends towards the middle.

I don't have enough experience with the puzzle to know if 5 words always suffice for published puzzles, but it seems likely. The two real puzzles I have tried have 2-word solutions from this program (13 characters, the minimum possible length for 2 words), while they were suggested to be solvable in 5 and 6 steps.

When it comes to randomly selected puzzles, there are possible boards for which I find a best answer of 7 words (using a modified version of the program)

68 candidate words
zany - yak - kc - can - numb - beaux - xv 7/23
zany - ye - exec - cab - bevy - yuk - km 7/22
zany - yuk - km - me - eve - exec - cab 7/21
Checked 3516880 sequences

and other sequences which have no solutions up to 7 words and use a lot of RAM and time before giving no solution (11GB peak resident size, 16 seconds):

194 candidate words
Checked 636279365 sequences

I didn't go full dynamic-programming but I did track the best way to each each of the 12*4096 states and stop recursing if the new candidate doesn't reach a known state faster. This runs much faster and uses less memory; the 11GB & 16s example with no solution above is now <4MB and <.01s! Runs below are with that version. (and apparently using a different dictionary, some runs were on debian oldstable and some on debian stable)

214 candidate words
Checked 1944 sequences

I also found that there are 8-word puzzles:

69 candidate words
jam - ma - aqua - ah - ho - oz - zip - pick 8/22
Checked 854 sequences
and 9+-word but I don't like some of the words (and the app doesn't accept 2-letter words):
153 candidate words
jerk - kc - ca - ax - xi - icky - yd - dz - zen 9/23
Checked 1038 sequences

Source code (GPL-3.0 license) (build with g++ -std=c++20 -O2):

Faster version:

Original version:


8 June 2023, 22:51 UTC

Conservation of Experience

If the universe were just, Loretta Gupta, Ph.D. would be forever remembered as the discoverer of Qualium, the researcher who shattered the hard problem of consciousness. Sadly, the universe is not just.

Dr. Gupta's discovery began with the observation that after interacting with Large Language Models (LLMs), her postdoctoral research assistants were listless and bored; hardly their usual energetic young selves. Not to mention useless for doing theoretical physics research. Most likely she was noticing a spurious correlation, but Gupta was intrigued and set up an informal experiment to disprove the link.

Experimental Group A directly interacted with LLM software and had the strongest effect. Control groups C (interacting with non-LLM software), D (non-LLM software in use, non-interactive) and E (no software) were unaffected. Most startling was the outcome for Experimental Group B, who were in a room with a computer executing LLM software, but with no inputs and outputs. For members of Group B, the degree of flattened affect was indistinguishable from group A.

Amused, she published her findings on arXiv under a pseudonym and moved on. That is, until it was cited by Emi Suzuiki in a paper that found that the performance of a synthetic nematode connectome was measurably degraded when it occupied the same shared hosting server as LLM software.

Gupta and the Suzuki collaborated on their next paper, establishing a causal relationship between LLM software usage and impaired neural activity, both in natural and simulated neurons. After use of LLM software was discontinued the neural activity initially appeared to return to baseline after a period of time. The problem was that Suzuki measured a higher baseline than Gupta, even for the identical synthetic connectome on identical hardware.

Gupta's lab, as it turns out, was in a building shared with the business school. The MBAs had long since incorporated use of LLMs into every aspect of their work: writing business plans, evaluating business plans, drafting e-mails, summarizing e-mails, generating dataing profiles, sifting through dating profiles for good matches. You name it, they were doing it with LLMs.

The evidence gathered by Gupta and Suzuki soon became crystal clear: neural activity and deep learning software both use, and potentially exhaust, some physical feature of spacetime not previously identified. Their next paper coined the term "Qualium", detailed construction of a 7-neuron device to measure the ambient level, and clearly showed that the Earth's baseline level of Qualium had been depleted by 2% since Suzuki's paper.

The fact is, we now face two ecological catastrophes: anthropogenic climate change ,and now Qualium exhaustion. World leaders have spent the last few decades saying much and doing little about the former. What, if anything, will they do about the impending Qualium shortage? In both cases, taking real action seems to risk the "line go up" nature of the global economic system. Well, I for one hope that we run out of Qualium before we cook in an atmosphere of CO2. By all acccounts the ego-death of Qualium deprivation is a painless one. I just regret that my animated corpse will probably continue serving the ends of capitalism until it, too, dies. I'm just glad I won't be there to experience it.


11 May 2023, 12:27 UTC

Xerox 820 & CP/M

My friend Steve was at an estate sale that had some pretty cool old computing stuff. (there had been an IMSAI priced at $300 but someone else bought it) He invited me to go back out the next day, when things were 50% off. I said "sure", mostly because I wanted a chance to hang out with Steve. No plans to buy anything, no way!

(this article is part narrative, part notes for myself, so it'll be rambling and updated as I think of things to say)

read more…

27 March 2023, 0:01 UTC

Welcome to the Polity

7 November 2022, 1:35 UTC

Local coordinate systems in OpenSCAD

4 September 2022, 2:11 UTC

Recent keyboard deeds

All older entries
Website Copyright © 2004-2024 Jeff Epler