
    
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
            
{"version":"https:\/\/jsonfeed.org\/version\/1","title":"mathspp.com feed","home_page_url":"https:\/\/mathspp.com\/blog","feed_url":"https:\/\/mathspp.com\/blog.json","description":"Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com.","author":{"name":"Rodrigo Gir\u00e3o Serr\u00e3o"},"items":[{"title":"Personal highlights of PyCon Lithuania 2026","date_published":"2026-04-11T14:23:00+02:00","id":"https:\/\/mathspp.com\/blog\/personal-highlights-of-pycon-lithuania-2026","url":"https:\/\/mathspp.com\/blog\/personal-highlights-of-pycon-lithuania-2026","content_html":"<p>In this article I share my personal highlights of PyCon Lithuania 2026.<\/p>\n\n<h2 id=\"shout-out-to-the-organisers-and-volunteers\">Shout out to the organisers and volunteers<a href=\"#shout-out-to-the-organisers-and-volunteers\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>This was my second time at PyCon Lithuania and, for the second time in a row, I leave with the impression that everything was very well organised and smooth.\nMaybe the organisers and volunteers were stressed out all the time &mdash; organising a conference is never easy &mdash; but everything looked under control all the time and well thought-through.<\/p>\n<p>Thank you for an amazing experience!<\/p>\n<p>And by the way, congratulations for 15 years of PyCon Lithuania.\nTo celebrate, they even served a gigantic cake during the first networking event.\nThe cake was <em>at least<\/em> 80cm by 30cm:<\/p>\n<figure class=\"image-caption\"><img title=\"The PyCon Lithuania cake.\" alt=\"A picture of a large rectangular cake with the PyCon Lithuania logo in the middle.\" src=\"\/user\/pages\/02.blog\/personal-highlights-of-pycon-lithuania-2026\/_cake.webp\"><figcaption class=\"\">The PyCon Lithuania cake.<\/figcaption><\/figure><p>I'll be honest with you: I didn't expect the cake to be good.\nThe quality of food tends to degrade when it's cooked at a large scale...\nBut even the taste was great and the cake had three coloured layers in yellow, green, and red.<\/p>\n<h2 id=\"social-activities\">Social activities<a href=\"#social-activities\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The organisers prepared <em>two<\/em> networking events, a speakers' dinner, and three city tours (one per evening) for speakers.\nThere was <em>always<\/em> something for you to do.<\/p>\n<p>The city tour is a brilliant idea and I wonder why more conferences don't do it:<\/p>\n<ul><li>Participants get to know a bit more of the city that's hosting the conference.<\/li>\n<li>Participants get the chance to talk to each other in a relaxed and informal environment.<\/li>\n<li>Hiring a tour guide is typically fairly cheap, especially when compared to organising a full-blown social event in a dedicated venue and with dedicated catering.<\/li>\n<\/ul><p>I had taken the city tour last time I had been at PyCon Lithuania and taking it again was not a mistake.\nHere's our group at the end of the tour, immediately before the speakers' dinner:<\/p>\n<figure class=\"image-caption\"><img title=\"Some PyCon Lithuania speakers at the city tour.\" alt=\"Some PyCon Lithuania speakers smile at the camera in front of Gediminas's castle.\" src=\"\/user\/pages\/02.blog\/personal-highlights-of-pycon-lithuania-2026\/_tour.webp\"><figcaption class=\"\">Some PyCon Lithuania speakers at the city tour.<\/figcaption><\/figure><p>The conference organisers even made sure that the city tour ended close to the location of the speakers' dinner <em>and<\/em> that the tour ended at the same time as the dinner started.\nAnother small detail that was carefully planned.<\/p>\n<p>The atmosphere of the restaurant was very pleasant and the staff there was helpful and kind, so we had a wonderful night.\nAt some point, at our table, we noticed that the folks at the other two tables were projecting something on a big screen.\nThere was a large curtain that partially separated our table from the other two, so we took some time to realise that an impromptu Python quiz was about to take place.<\/p>\n<p>I'm (way too) competitive and immediately got up to play.\nAfter six questions, which included learning about the existence of the web framework <em>Falcon<\/em> and correctly reordering the first four sentences of the Zen of Python, I was crowned the winner:<\/p>\n<figure class=\"image-caption\"><img title=\"The final score for the quiz.\" alt=\"A slanted picture of a blue screen showing the player RGS at the top of the quiz podium.\" src=\"\/user\/pages\/02.blog\/personal-highlights-of-pycon-lithuania-2026\/_quiz.webp\"><figcaption class=\"\">The final score for the quiz.<\/figcaption><\/figure><p>The top three players got a <em>free<\/em> spin on the PyCon Lithuania wheel of fortune.<\/p>\n<h2 id=\"egg-hunt-and-swag\">Egg hunt and swag<a href=\"#egg-hunt-and-swag\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>On each day of the conference there was an egg hunt running...<\/p>","summary":"In this article I share my personal highlights of PyCon Lithuania 2026.","date_modified":"2026-04-13T17:33:19+02:00","tags":["python","conferences","opinion"],"image":"\/user\/pages\/02.blog\/personal-highlights-of-pycon-lithuania-2026\/thumbnail.webp"},{"title":"Who wants to be a millionaire: iterables edition","date_published":"2026-04-09T23:17:00+02:00","id":"https:\/\/mathspp.com\/blog\/who-wants-to-be-a-millionaire-iterables-edition","url":"https:\/\/mathspp.com\/blog\/who-wants-to-be-a-millionaire-iterables-edition","content_html":"<p>Play this short quiz to test your Python knowledge!<\/p>\n\n<script src=\"\/user\/themes\/myquark\/js\/quiz.js\"><\/script>\n<link rel=\"stylesheet\" href=\"\/user\/themes\/myquark\/css\/quiz-custom.css\">\n<p>At PyCon Lithuania 2026 I did a lightning talk where I presented a \u201cWho wants to be a millionaire?\u201d Python quiz, themed around iterables.\nThere's a whole performance during the lightning talk which was recorded and will be eventually linked to from here.\nThis article includes only the four questions, the options presented, and a basic system that allows you to check whether you got it right or not.<\/p>\n<h2 id=\"question-1\">Question 1<a href=\"#question-1\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>This is an easy one to get you started.\nIt makes more sense if you watch the <em>performance<\/em> of the lightning talk.<\/p>\n<div class=\"quiz-question\" data-correct=\"a\">\n  <div class=\"question-text\"><p>What is the output of the following Python program?<\/p><\/div>\n  <pre><code class=\"language-py hljs language-python\">print(\"Hello, world!\")<\/code><\/pre>\n  <ul class=\"choices\"><li data-option=\"a\">Hello, world!<\/li>\n    <li data-option=\"b\">Hello world!<\/li>\n    <li data-option=\"c\">Hello world<\/li>\n    <li data-option=\"d\">Hello world!!<\/li>\n  <\/ul><p class=\"feedback\"><\/p>\n<\/div>\n<h2 id=\"question-2\">Question 2<a href=\"#question-2\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<div class=\"quiz-question\" data-correct=\"a\">\n  <div class=\"question-text\"><p>What is the output of the following Python program?<\/p><\/div>\n  <pre><code class=\"language-py hljs language-python\">squares = (x ** 2 for x in range(3))\nprint(type(squares))<\/code><\/pre>\n  <ul class=\"choices\"><li data-option=\"a\"><code>&lt;class 'generator'&gt;<\/code><\/li>\n    <li data-option=\"b\"><code>&lt;class 'gen_expr'&gt;<\/code><\/li>\n    <li data-option=\"c\"><code>&lt;class 'list'&gt;<\/code><\/li>\n    <li data-option=\"d\"><code>&lt;class 'tuple'&gt;<\/code><\/li>\n  <\/ul><p class=\"feedback\"><\/p>\n<\/div>\n<h2 id=\"question-3\">Question 3<a href=\"#question-3\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>This was a reference to the talk I'd given earlier today, where I talked about <code>tee<\/code>.\nThe only object in <code>itertools<\/code> that is not an iterable.<\/p>\n<div class=\"quiz-question\" data-correct=\"a\">\n  <div class=\"question-text\"><p>Out of the 20, how many objects in <code>itertools<\/code> are iterables?<\/p><\/div>\n  <ul class=\"choices\"><li data-option=\"a\">19<\/li>\n    <li data-option=\"b\">20<\/li>\n    <li data-option=\"c\">1<\/li>\n    <li data-option=\"d\">0<\/li>\n  <\/ul><p class=\"feedback\"><\/p>\n<\/div>\n<h2 id=\"question-4\">Question 4<a href=\"#question-4\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<div class=\"quiz-question\" data-correct=\"d\">\n  <div class=\"question-text\"><p>What is the output of the following Python program?<\/p><\/div>\n  <pre><code class=\"language-py hljs language-python\">from itertools import *\n\nprint(sum(chain.from_iterable(chain(*next(\nislice(permutations(islice(batched(pairwise(\ncount()),5),3,9)),15,None)))))<\/code><\/pre>\n  <ul class=\"choices\"><li data-option=\"a\">1800<\/li>\n    <li data-option=\"b\">0<\/li>\n    <li data-option=\"c\">\ud83c\uddf1\ud83c\uddf9\u2764\ufe0f\ud83d\udc0d<\/li>\n    <li data-option=\"d\"><code>SyntaxError<\/code><\/li>\n  <\/ul><p class=\"feedback\"><\/p>\n<\/div>","summary":"Play this short quiz to test your Python knowledge!","date_modified":"2026-04-09T22:29:23+02:00","tags":["python","quiz"],"image":"\/user\/pages\/02.blog\/who-wants-to-be-a-millionaire-iterables-edition\/thumbnail.webp"},{"title":"uv skills for coding agents","date_published":"2026-04-09T14:19:00+02:00","id":"https:\/\/mathspp.com\/blog\/uv-skills","url":"https:\/\/mathspp.com\/blog\/uv-skills","content_html":"<p>This article shares two skills you can add to your coding agents so they use uv workflows.<\/p>\n\n<p>I have fully adopted uv into my workflows and most of the time I want my coding agents to use uv workflows as well, like when running any Python code or managing and running scripts that may or may not have dependencies.<\/p>\n<p>To make this more convenient for me, I created two <code>SKILL.md<\/code> files for two of the most common workflows that the coding agents get wrong on the first few tries:<\/p>\n<ol><li><code>python-via-uv<\/code>: this skill tells the agent that it should use uv whenever it wants to run any piece of Python code, be it one-liners or scripts. This is relevant because I don't even have the command <code>python<\/code>\/<code>python3<\/code> in the shell path, so whenever the LLM tries running something with <code>python ...<\/code>, it fails.<\/li>\n<li><code>uv-script-workflow<\/code>: this skill is specifically for when the agent wants to create and run a script. It instructs the LLM to initalise the script with <code>uv init --script ...<\/code> and then tells it about the relevant commands to manage the script dependencies.<\/li>\n<\/ol><p>The two skills also add a note about sandboxing, since uv's default cache directory will be outside your sandbox.\nWhen that's the case, the agent is already instructed to use a valid temporary location for the uv cache.<\/p>\n<p><em>Installing<\/em> a skill usually just means dropping a Markdown file in the correct folder, but you should check the documentation for the tools you use.<\/p>\n<p>Here are the two skills for you to download:<\/p>\n<ol><li><a href=\"\/blog\/uv-skills\/.\/SKILL-python-via-uv.txt\">Skill for <code>python-via-uv<\/code><\/a><\/li>\n<li><a href=\"\/blog\/uv-skills\/.\/SKILL-uv-script-workflow.txt\">Skill for <code>uv-script-workflow<\/code><\/a><\/li>\n<\/ol><p>I also included the skills verbatim here, for your convenience:<\/p>\n<details><summary>Skill for <code>python-via-uv<\/code><\/summary><pre><code class=\"language-markdown\">---\nname: python-via-uv\ndescription: Enforce Python execution through `uv` instead of direct interpreter calls. Use when Codex needs to run Python scripts, modules, one-liners, tools, test runners, or package commands in a workspace and should avoid invoking `python` or `python3` directly.\n---\n\n# Python Via Uv\n\nUse `uv` for every Python command.\n\nDo not run `python`.\nDo not run `python3`.\nDo not suggest `python` or `python3` in instructions unless the user explicitly requires them and the constraint must be called out as a conflict.\n\n## Execution Rules\n\nWhen sandboxed, set `UV_CACHE_DIR` to a temporary directory the agent can write to before running `uv` commands.\n\nPrefer these patterns:\n\n- Run a script: `UV_CACHE_DIR=\/tmp\/uv-cache uv run path\/to\/script.py`\n- Run a module: `UV_CACHE_DIR=\/tmp\/uv-cache uv run -m package.module`\n- Run a one-liner: `UV_CACHE_DIR=\/tmp\/uv-cache uv run python -c \"print('hello')\"`\n- Run a tool exposed by dependencies: `UV_CACHE_DIR=\/tmp\/uv-cache uv run tool-name`\n- Add a dependency for an ad hoc command: `UV_CACHE_DIR=\/tmp\/uv-cache uv run --with &lt;package&gt; python -c \"...\"`\n\n## Notes\n\nUsing `python` inside `uv run ...` is acceptable because `uv` is still the entrypoint controlling interpreter selection and environment setup.\n\nIf the workspace already defines a project-specific temporary cache directory, prefer that over `\/tmp\/uv-cache`.\n\nIf a command example or existing documentation uses `python` or `python3` directly, translate it to the closest `uv` form before executing it....<\/code><\/pre><\/details>","summary":"This article shares two skills you can add to your coding agents so they use uv workflows.","date_modified":"2026-04-09T13:39:48+02:00","tags":["python","programming","LLM","uv"],"image":"\/user\/pages\/02.blog\/uv-skills\/thumbnail.webp"},{"title":"Indexable iterables","date_published":"2026-04-03T13:41:00+02:00","id":"https:\/\/mathspp.com\/blog\/indexable-iterables","url":"https:\/\/mathspp.com\/blog\/indexable-iterables","content_html":"<p>Learn how objects are automatically iterable if you implement integer indexing.<\/p>\n\n<h2 id=\"introduction\">Introduction<a href=\"#introduction\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>An <strong>iterable<\/strong> in Python is any object you can traverse through with a <code>for<\/code> loop.\n<strong>Iterables<\/strong> are typically containers and iterating over the iterable object allows you to access the elements of the container.<\/p>\n<p>This article will show you how you can create your own iterable objects through the implementation of integer indexing.<\/p>\n<h2 id=\"indexing-with-getitem\">Indexing with <code>__getitem__<\/code><a href=\"#indexing-with-getitem\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To make an object that can be indexed you need to implement the method <code>__getitem__<\/code>.<\/p>\n<p>As an example, you'll implement a class <code>ArithmeticSequence<\/code> that represents an <strong>arithmetic sequence<\/strong>, like <span class=\"mathjax mathjax--inline\">\\(5, 8, 11, 14, 17, 20\\)<\/span>.\nAn arithmetic sequence is defined by its first number (<span class=\"mathjax mathjax--inline\">\\(5\\)<\/span>), the step between numbers (<span class=\"mathjax mathjax--inline\">\\(3\\)<\/span>), and the total number of elements (<span class=\"mathjax mathjax--inline\">\\(6\\)<\/span>).\nThe sequence <span class=\"mathjax mathjax--inline\">\\(5, 8, 11, 14, 17, 20\\)<\/span> is <code>seq = ArithmeticSequence(5, 3, 6)<\/code> and <code>seq[3]<\/code> should be <span class=\"mathjax mathjax--inline\">\\(14\\)<\/span>.\nUsing some arithmetic, you can implement indexing in <code>__getitem__<\/code> directly:<\/p>\n<pre><code class=\"language-py\">class ArithmeticSequence:\n    def __init__(self, start: int, step: int, total: int) -&gt; None:\n        self.start = start\n        self.step = step\n        self.total = total\n\n    def __getitem__(self, index: int) -&gt; int:\n        if not 0 &lt;= index &lt; self.total:\n            raise IndexError(f\"Invalid index {index}.\")\n\n        return self.start + index * self.step\n\nseq = ArithmeticSequence(5, 3, 6)\nprint(seq[3])  # 14<\/code><\/pre>\n<h2 id=\"turning-an-indexable-object-into-an-iterable\">Turning an indexable object into an iterable<a href=\"#turning-an-indexable-object-into-an-iterable\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>If your object accepts integer indices, then it is <em>automatically<\/em> an iterable.\nIn fact, you can already iterate over the sequence you created above by simply using it in a <code>for<\/code> loop:<\/p>\n<pre><code class=\"language-py\">for value in seq:\n    print(value, end=\", \")\n# 5, 8, 11, 14, 17, 20,<\/code><\/pre>\n<h2 id=\"how-python-distinguishes-iterables-from-non-iterables\">How Python distinguishes iterables from non-iterables<a href=\"#how-python-distinguishes-iterables-from-non-iterables\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>You might ask yourself &ldquo;how does Python inspect <code>__getitem__<\/code> to see it uses numeric indices?&rdquo;\nIt doesn't!\nIf your object implements <code>__getitem__<\/code> and you try to use it as an iterable, Python will <em>try<\/em> to iterate over it.\nIt either works or it doesn't!<\/p>\n<p>To illustrate this point, you can define a class <code>DictWrapper<\/code> that wraps a dictionary and implements <code>__getitem__<\/code> by just grabbing the corresponding item out of a dictionary:<\/p>\n<pre><code class=\"language-py\">class DictWrapper:\n    def __init__(self, values):\n        self.values = values\n\n    def __getitem__(self, index):\n        return self.values[index]<\/code><\/pre>\n<p>Since <code>DictWrapper<\/code> implements <code>__getitem__<\/code>, if an instance of <code>DictWrapper<\/code> just <em>happens<\/em> to have some integer keys (starting at <code>0<\/code>) then you'll be able to iterate partially over the dictionary:<\/p>\n<pre><code class=\"language-py\">d1 = DictWrapper({0: \"hey\", 1: \"bye\", \"key\": \"value\"})\n\nfor value in d1:\n    print(value)<\/code><\/pre>\n<pre><code class=\"language-pycon\">hey\nbye\nTraceback (most recent call last):\n  File \"&lt;python-input-25&gt;\", line 3, in &lt;module&gt;\n    for value in d1:\n                 ^^\n  File \"&lt;python-input-18&gt;\", line 6, in __getitem__\n    return self.values[index]\n           ~~~~~~~~~~~^^^^^^^\nKeyError: 2<\/code><\/pre>\n<p>What's interesting is that you can see explicitly that Python tried to index the object <code>d<\/code> with the key <code>2<\/code> and it didn't work.\nIn the <code>ArithmeticSequence<\/code> above, you didn't get an error because you raised <code>IndexError<\/code> when you reached the end and that's how Python understood the iteration was done.\nIn this case, since you get a <code>KeyError<\/code>, Python doesn't understand what's going on and just...<\/p>","summary":"Learn how objects are automatically iterable if you implement integer indexing.","date_modified":"2026-04-03T15:09:51+02:00","tags":["python","programming","dunder methods"],"image":"\/user\/pages\/02.blog\/indexable-iterables\/thumbnail.webp"},{"title":"Ask the LLM to write code for it","date_published":"2026-03-24T14:16:00+01:00","id":"https:\/\/mathspp.com\/blog\/ask-the-llm-to-write-code-for-it","url":"https:\/\/mathspp.com\/blog\/ask-the-llm-to-write-code-for-it","content_html":"<p>This article covers a useful LLM pattern where you ask the LLM to write code to solve a problem instead of asking it to solve the problem directly.<\/p>\n\n<h2 id=\"the-problem-of-merging-two-transcripts\">The problem of merging two transcripts<a href=\"#the-problem-of-merging-two-transcripts\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>I had two files that contained two halves of the transcript of an audio recording and I wanted to use an LLM to merge the two halves.\nThere were three reasons that stopped me from simply copying part 2 and pasting it after part 1:<\/p>\n<ol>\n<li>the two transcripts overlapped (the end of part 1 was after the start of part 2);<\/li>\n<li>the timestamps for part 2 started from 0, so they were missing an offset; and<\/li>\n<li>speaker identification was not consistent.<\/li>\n<\/ol>\n<p>I uploaded the two halves into ChatGPT and asked it to merge the two transcripts, fix the timestamps and the speaker identification, but to not change the text.<\/p>\n<p>The result I got back was a ridiculous attempt at providing the full transcript, with two sections that supposedly represented parts of either transcript I could just copy and paste confidently, and a couple of other ridiculous blunders.<\/p>\n<p>Instead of fighting ChatGPT, I decided to use a very useful pattern I learned about last year.<\/p>\n<h2 id=\"ask-the-llm-to-write-code-for-it\">Ask the LLM to write code for it<a href=\"#ask-the-llm-to-write-code-for-it\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Instead of asking ChatGPT to merge the transcripts, I could ask it to analyse them, find the solutions to the three problems listed above, and then write code that would merge the transcripts.<\/p>\n<p>Since I was confident that ChatGPT could<\/p>\n<ol>\n<li>identify the overlap between the two files;<\/li>\n<li>use the overlap information to compute the timestamp offset required for part 2; and<\/li>\n<li>figure out you had to swap the two speakers in part 2,<\/li>\n<\/ol>\n<p>I knew ChatGPT would be able to write a Python script that could read from both files and apply a couple of string operations to the second part.<\/p>\n<p>This yielded much better results in two ways.\nChatGPT was able to find the solutions for the three problems above and write a script that fixed them automatically.\nThat was the goal.<\/p>\n<p>On top of that, since ChatGPT had a very clear implicit goal \u2014 get the final merged transcript \u2014 and since running Python code is something that ChatGPT can do, ChatGPT even ran the script for me and produced two artifacts at the end:<\/p>\n<ol>\n<li>the full Python script I could run against the two halves if I wanted; and<\/li>\n<li>the final, fixed transcript.<\/li>\n<\/ol>\n<p>This is an example application of a really useful LLM pattern:<\/p>\n<blockquote>\n<p>Don't ask the LLM to solve a problem. Instead, ask it to write code that solves the problem.<\/p>\n<\/blockquote>\n<p>As another visual example, it's much easier to ask an LLM to write a Python script that draws a path that solves a maze (that's just a couple hundred of lines of code) than it is to upload an image and ask the LLM to draw a <em>valid path<\/em> on the picture of a maze.\nTry it yourself!<\/p>","summary":"This article covers a useful LLM pattern where you ask the LLM to write code to solve a problem instead of asking it to solve the problem directly.","date_modified":"2026-03-24T15:29:56+01:00","tags":["python","programming","llm","slice of life"],"image":"\/user\/pages\/02.blog\/ask-the-llm-to-write-code-for-it\/thumbnail.webp"},{"title":"Cyclic trapezoid animation","date_published":"2026-03-14T15:51:00+01:00","id":"https:\/\/mathspp.com\/blog\/cyclic-trapezoid-animation","url":"https:\/\/mathspp.com\/blog\/cyclic-trapezoid-animation","content_html":"<p>See an animation of a trapezoid innscribed in a circle, built with some maths and the help of an LLM.<\/p>\n\n<h2 id=\"the-animation\">The animation<a href=\"#the-animation\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>My brother asked for my help to build an animation of a trapezoid inscribed in a circle that kept changing his shape.\nWith <a href=\"\/blog\/til\/cyclic-quadrilateral\">a bit of maths<\/a> and the help of ChatGPT for the UI, I created the animation you can see below.\nUnder the animation you can find a control panel that allows you to tweak some animation parameters, and under that you can find a brief explanation of how the animation works.<\/p>\n<style>\n  .cyclic-trapezoid-embed {\n    --ctp-panel-bg: color-mix(in srgb, var(--bg, #0b1020) 88%, transparent);\n    --ctp-panel-border: color-mix(in srgb, var(--tx, #e5e7eb) 18%, transparent);\n    --ctp-text: var(--tx, #e5e7eb);\n    --ctp-muted: color-mix(in srgb, var(--tx, #e5e7eb) 65%, transparent);\n    --ctp-input-bg: color-mix(in srgb, var(--bg, #0b1020) 92%, black 8%);\n\n    width: 100%;\n    box-sizing: border-box;\n  }\n\n  .cyclic-trapezoid-embed *,\n  .cyclic-trapezoid-embed *::before,\n  .cyclic-trapezoid-embed *::after {\n    box-sizing: border-box;\n  }\n\n  .cyclic-trapezoid-embed .ctp-canvas-wrap {\n    width: 100%;\n  }\n\n  .cyclic-trapezoid-embed canvas {\n    display: block;\n    width: 100%;\n    height: min(70vh, 800px);\n    min-height: 360px;\n    background: var(--bg, #0b1020);\n    border-radius: 16px;\n  }\n\n  .cyclic-trapezoid-embed .ctp-ui {\n    margin-top: 16px;\n    width: 100%;\n    border-radius: 14px;\n    background: var(--ctp-panel-bg);\n    color: var(--ctp-text);\n    border: 1px solid var(--ctp-panel-border);\n    backdrop-filter: blur(8px);\n    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);\n    font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n    overflow: hidden;\n  }\n\n  .cyclic-trapezoid-embed .ctp-summary {\n    list-style: none;\n    cursor: pointer;\n    padding: 14px;\n    font-size: 18px;\n    font-weight: 700;\n    user-select: none;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n\n  .cyclic-trapezoid-embed .ctp-summary::-webkit-details-marker {\n    display: none;\n  }\n\n  .cyclic-trapezoid-embed .ctp-ui-content {\n    padding: 0 14px 14px;\n  }\n\n  .cyclic-trapezoid-embed fieldset {\n    margin: 0 0 12px;\n    padding: 10px 10px 6px;\n    border-radius: 10px;\n    border: 1px solid var(--ctp-panel-border);\n    min-width: 0;\n  }\n\n  .cyclic-trapezoid-embed legend {\n    padding: 0 6px;\n    color: var(--ctp-text);\n    font-weight: 600;\n    font-size: 14px;\n  }\n\n  .cyclic-trapezoid-embed .row {\n    display: grid;\n    grid-template-columns: 1fr auto;\n    gap: 10px;\n    align-items: center;\n    margin-bottom: 8px;\n  }\n\n  .cyclic-trapezoid-embed .row label {\n    font-size: 13px;\n    color: var(--ctp-text);\n  }\n\n  .cyclic-trapezoid-embed .row .value {\n    color: var(--ctp-muted);\n    font-size: 12px;\n    min-width: 64px;\n    text-align: right;\n    font-variant-numeric: tabular-nums;\n  }\n\n  .cyclic-trapezoid-embed .control {\n    display: grid;\n    grid-template-columns: 1fr;\n    gap: 4px;\n    margin-bottom: 10px;\n  }\n\n  .cyclic-trapezoid-embed .control label {\n    font-size: 13px;\n    color: var(--ctp-text);\n  }\n\n  .cyclic-trapezoid-embed input[type=\"range\"],\n  .cyclic-trapezoid-embed input[type=\"number\"],\n  .cyclic-trapezoid-embed input[type=\"color\"] {\n    width: 100%;\n  }\n\n  .cyclic-trapezoid-embed input[type=\"number\"] {\n    padding: 6px 8px;\n    border-radius: 8px;\n    border: 1px solid var(--ctp-panel-border);\n    background: var(--ctp-input-bg);\n    color: var(--ctp-text);\n  }\n\n  .cyclic-trapezoid-embed input[type=\"color\"] {\n    height: 36px;\n    padding: 0;\n    border: none;\n    background: transparent;\n    cursor: pointer;\n  }\n\n  .cyclic-trapezoid-embed .buttons {\n    display: flex;\n    gap: 8px;\n    flex-wrap: wrap;\n  }\n\n  .cyclic-trapezoid-embed button {\n    appearance: none;\n    border: 1px solid var(--ctp-panel-border);\n    background: color-mix(in srgb, var(--bg, #0b1020) 80%, var(--tx, #e5e7eb) 8%);\n    color: var(--ctp-text);\n    border-radius: 10px;\n    padding: 9px 12px;\n    cursor: pointer;\n    font-weight: 600;\n  }\n\n  .cyclic-trapezoid-embed button:hover {\n    background: color-mix(in srgb, var(--bg, #0b1020) 72%, var(--tx, #e5e7eb) 14%);\n  }\n\n  .cyclic-trapezoid-embed .hint {\n    font-size: 12px;\n    color: var(--ctp-muted);\n    line-height: 1.35;\n    margin-top: 6px;\n  }\n\n  .cyclic-trapezoid-embed code {\n    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n  }\n<\/style><div class=\"cyclic-trapezoid-embed\" id=\"cyclic-trapezoid-embed\">\n  <div class=\"ctp-canvas-wrap\">\n    <canvas id=\"ctp-canvas\"><\/canvas><\/div>\n\n  <details class=\"ctp-ui\"><summary class=\"ctp-summary\">Cyclic trapezoid controls<\/summary><div class=\"ctp-ui-content\">\n      <fieldset><legend>Colour<\/legend>\n\n        <div class=\"control\">\n          <label for=\"ctp-bgColor\">Background colour<\/label>\n          <input id=\"ctp-bgColor\" type=\"color\" value=\"#0b1020\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-circleColor\">Circle colour<\/label>\n          <input id=\"ctp-circleColor\" type=\"color\" value=\"#94a3b8\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-pointColor\">Points colour<\/label>\n          <input id=\"ctp-pointColor\" type=\"color\" value=\"#f8fafc\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-parallelColor\">Parallel sides colour<\/label>\n          <input id=\"ctp-parallelColor\" type=\"color\" value=\"#fbbf24\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-nonParallelColor\">Non-parallel sides colour<\/label>\n          <input id=\"ctp-nonParallelColor\" type=\"color\" value=\"#60a5fa\"><\/div>\n      <\/fieldset><fieldset><legend>Point A<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-aPeriod\">Period<\/label>\n          <div class=\"value\" id=\"ctp-aPeriodValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-aPeriod\" type=\"range\" min=\"2\" max=\"60\" step=\"0.1\" value=\"11\"><\/fieldset><fieldset><legend>Point B<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-bMax\">Max angle<\/label>\n          <div class=\"value\" id=\"ctp-bMaxValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-bMax\" type=\"range\" min=\"15\" max=\"180\" step=\"1\" value=\"165\"><div class=\"row\">\n          <label for=\"ctp-bPeriod\">Period<\/label>\n          <div class=\"value\" id=\"ctp-bPeriodValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-bPeriod\" type=\"range\" min=\"2\" max=\"60\" step=\"0.1\" value=\"7\"><div class=\"row\">\n          <label for=\"ctp-bPhase\">Phase<\/label>\n          <div class=\"value\" id=\"ctp-bPhaseValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-bPhase\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" value=\"0\"><\/fieldset><fieldset><legend>Point D<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-dMax\">Max angle<\/label>\n          <div class=\"value\" id=\"ctp-dMaxValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-dMax\" type=\"range\" min=\"15\" max=\"180\" step=\"1\" value=\"90\"><div class=\"row\">\n          <label for=\"ctp-dPeriod\">Period<\/label>\n          <div class=\"value\" id=\"ctp-dPeriodValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-dPeriod\" type=\"range\" min=\"2\" max=\"60\" step=\"0.1\" value=\"5\"><div class=\"row\">\n          <label for=\"ctp-dPhase\">Phase<\/label>\n          <div class=\"value\" id=\"ctp-dPhaseValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-dPhase\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" value=\"60\"><\/fieldset><fieldset><legend>Global<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-radius\">Circle radius<\/label>\n          <div class=\"value\" id=\"ctp-radiusValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-radius\" type=\"range\" min=\"0.1\" max=\"0.48\" step=\"0.01\" value=\"0.35\"><div class=\"row\">\n          <label for=\"ctp-speed\">Global animation speed<\/label>\n          <div class=\"value\" id=\"ctp-speedValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-speed\" type=\"range\" min=\"0\" max=\"4\" step=\"0.01\" value=\"1\"><\/fieldset><div class=\"buttons\">\n        <button id=\"ctp-resetTime\" type=\"button\">Reset animation time<\/button>\n      <\/div>\n\n      <div class=\"hint\">\n        Point C is computed from the cyclic trapezoid rule:...<\/div><\/div><\/details><\/div>","summary":"See an animation of a trapezoid innscribed in a circle, built with some maths and the help of an LLM.","date_modified":"2026-03-14T17:35:14+01:00","tags":["mathematics","geometry","visualisation","llm"],"image":"\/user\/pages\/02.blog\/cyclic-trapezoid-animation\/thumbnail.webp"},{"title":"TIL #142 \u2013 Cyclic quadrilateral","date_published":"2026-03-14T14:58:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/cyclic-quadrilateral","url":"https:\/\/mathspp.com\/blog\/til\/cyclic-quadrilateral","content_html":"<p>Today I learned that cyclic quadrilaterals have supplementary opposite angles.<\/p>\n\n<p>A <strong>cyclic quadrilateral<\/strong> \u2014 a quadrilateral whose four vertices all lie on a single circle \u2014 has supplementary opposite angles.<\/p>\n<p>This means that opposite angles add to 180 degrees, or <span class=\"mathjax mathjax--inline\">\\(\\pi\\)<\/span> radians.<\/p>\n<p>As it turns out, this is actually an equivalence relation.\nIf a quadrilateral has supplementary opposite angles, it's a cyclic quadrilateral.<\/p>\n<p>This fact about supplementary opposite angles was very useful for an animation I was trying to create...\nI may share it here later!<\/p>","summary":"Today I learned that cyclic quadrilaterals have supplementary opposite angles.","date_modified":"2026-03-14T16:03:32+01:00","tags":["mathematics","geometry"],"image":"\/user\/pages\/02.blog\/04.til\/142.cyclic-quadrilateral\/thumbnail.webp"},{"title":"TIL #141 \u2013 Inspect a lazy import","date_published":"2026-03-13T14:38:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/inspect-a-lazy-import","url":"https:\/\/mathspp.com\/blog\/til\/inspect-a-lazy-import","content_html":"<p>Today I learned how to inspect a lazy import object in Python 3.15.<\/p>\n\n<p>Python 3.15 comes with lazy imports and today I played with them for a minute.\nI defined the following module <code>mod.py<\/code>:<\/p>\n<pre><code class=\"language-py\">print(\"Hey!\")\n\ndef f():\n    return \"Bye!\"<\/code><\/pre>\n<p>Then, in the REPL, I could check that lazy imports indeed work:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; # Python 3.15\n&gt;&gt;&gt; lazy import mod\n&gt;&gt;&gt;<\/code><\/pre>\n<p>The fact that I didn't see a \"Hey!\" means that the import is, indeed, lazy.\nThen, I wanted to take a look at the module so I printed it, but that triggered reification (going from a lazy import to a regular module):<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; print(mod)\nHey!\n&lt;module 'mod' from '\/Users\/rodrigogs\/Documents\/tmp\/mod.py'&gt;<\/code><\/pre>\n<p>So, I checked <a href=\"https:\/\/peps.python.org\/pep-0810\/#reification\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">the PEP that introduced explicit lazy modules<\/a> and turns out as soon as you <em>reference<\/em> the lazy object directly, it gets reified.\nBut you can work around it by using <code>globals<\/code>:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; # Fresh 3.15 REPL\n&gt;&gt;&gt; lazy import mod\n&gt;&gt;&gt; globals()[\"mod\"]\n&lt;lazy_import 'mod'&gt;<\/code><\/pre>\n<p>This shows the new class <code>lazy_import<\/code> that was added to support lazy imports!<\/p>\n<p>Pretty cool, right?<\/p>","summary":"Today I learned how to inspect a lazy import object in Python 3.15.","date_modified":"2026-03-13T15:54:14+01:00","tags":["programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/141.inspect-a-lazy-import\/thumbnail.webp"},{"title":"TIL #140 \u2013 Install Jupyter with uv","date_published":"2026-03-03T16:16:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/install-jupyter-with-uv","url":"https:\/\/mathspp.com\/blog\/til\/install-jupyter-with-uv","content_html":"<p>Today I learned how to install jupyter properly while using uv to manage tools.<\/p>\n\n<h2 id=\"running-a-jupyter-notebook-server-or-jupyter-lab\">Running a Jupyter notebook server or Jupyter lab<a href=\"#running-a-jupyter-notebook-server-or-jupyter-lab\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To run a Jupyter notebook server with uv, you can run the command<\/p>\n<pre><code class=\"language-bash\">$ uvx jupyter notebook<\/code><\/pre>\n<p>Similarly, if you want to run Jupyter lab, you can run<\/p>\n<pre><code class=\"language-bash\">$ uvx jupyter lab<\/code><\/pre>\n<p>Both work, but uv will kindly present a message explaining how it's actually doing you a favour, because it <em>guessed<\/em> what you wanted.\nThat's because <code>uvx something<\/code> usually looks for a package named \u201csomething\u201d with a command called \u201csomething\u201d.<\/p>\n<p>As it turns out, the command <code>jupyter<\/code> comes from the package <code>jupyter-core<\/code>, not from the package <code>jupyter<\/code>.<\/p>\n<h2 id=\"installing-jupyter\">Installing Jupyter<a href=\"#installing-jupyter\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>If you're running Jupyter notebooks often, you can install the notebook server and Jupyter lab with<\/p>\n<pre><code class=\"language-bash\">$ uv tool install --with jupyter jupyter-core<\/code><\/pre>\n<h3 id=\"why-uv-tool-install-jupyter-fails\">Why <code>uv tool install jupyter<\/code> fails<a href=\"#why-uv-tool-install-jupyter-fails\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>Running <code>uv tool install jupyter<\/code> fails because the package <code>jupyter<\/code> doesn't provide any commands by itself.<\/p>\n<h3 id=\"why-uv-tool-install-jupyter-core-doesn-t-work\">Why <code>uv tool install jupyter-core<\/code> doesn't work<a href=\"#why-uv-tool-install-jupyter-core-doesn-t-work\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>The command <code>uv tool install jupyter-core<\/code> looks like it works because it installs the command <code>jupyter<\/code> correctly.\nHowever, if you use <code>--help<\/code> you can see that you don't have access to the subcommands you need:<\/p>\n<pre><code class=\"language-bash\">$ uv tool install jupyter-core\n...\nInstalled 3 executables: jupyter, jupyter-migrate, jupyter-troubleshoot\n$ jupyter --help\n...\nAvailable subcommands: book migrate troubleshoot<\/code><\/pre>\n<p>That's because the subcommands <code>notebook<\/code> and <code>lab<\/code> are from the package <code>jupyter<\/code>.\nThe solution?\nInstall <code>jupyter-core<\/code> <em>with<\/em> the additional dependency <code>jupyter<\/code>, which is what the command <code>uv tool install --with jupyter jupyter-core<\/code> does.<\/p>\n<h2 id=\"other-usages-of-jupyter\">Other usages of Jupyter<a href=\"#other-usages-of-jupyter\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The uv documentation has a <a href=\"https:\/\/docs.astral.sh\/uv\/guides\/integration\/jupyter\/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">page dedicated exclusively to the usage of uv with Jupyter<\/a>, so check it out for other use cases of the uv and Jupyter combo!<\/p>","summary":"Today I learned how to install jupyter properly while using uv to manage tools.","date_modified":"2026-03-03T18:05:58+01:00","tags":["python","programming","uv","productivity"],"image":"\/user\/pages\/02.blog\/04.til\/140.install-jupyter-with-uv\/thumbnail.webp"},{"title":"TIL #139 \u2013 Multiline input in the REPL","date_published":"2026-03-02T15:15:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/multiline-input-in-the-repl","url":"https:\/\/mathspp.com\/blog\/til\/multiline-input-in-the-repl","content_html":"<p>Today I learned how to do multiline input in the REPL using an uncommon combination of arguments for the built-in <code>open<\/code>.<\/p>\n\n<p>A while ago <a href=\"\/blog\/til\/020\">I learned I could use <code>open(0)<\/code> to open standard input<\/a>.\nThis unlocks a neat trick that allows you to do multiline input in the REPL:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; msg = open(0).read()\nHello,\nworld!\n^D\n&gt;&gt;&gt; msg\n'Hello,\\nworld!\\n'<\/code><\/pre>\n<p>The cryptic <code>^D<\/code> is <kbd>Ctrl<\/kbd>+<kbd>D<\/kbd>, which means EOF on Unix systems.\nIf you're on Windows, use <kbd>Ctrl<\/kbd>+<kbd>Z<\/kbd>.<\/p>\n<p>The problem is that if you try to use <code>open(0).read()<\/code> again to read more multiline input, you get an exception:<\/p>\n<pre><code class=\"language-py\">OSError: [Errno 9] Bad file descriptor<\/code><\/pre>\n<p>That's because, when you finished reading the first time around, Python closed the file descriptor <code>0<\/code>, so you can no longer use it.<\/p>\n<p>The fix is to set <code>closefd=False<\/code> when you use the built-in <code>open<\/code>.\nWith the parameter <code>closefd<\/code> set to <code>False<\/code>, the underlying file descriptor isn't closed and you can reuse it:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; msg1 = open(0, closefd=False).read()\nHello,\nworld!\n^D\n&gt;&gt;&gt; msg1\n'Hello,\\nworld!\\n'\n\n&gt;&gt;&gt; msg2 = open(0, closefd=False).read()\nGoodbye,\nworld!\n^D\n&gt;&gt;&gt; msg2\n'Goodbye,\\nworld!\\n'<\/code><\/pre>\n<p>By using <code>open(0, closefd=False)<\/code>, you can read multiline input in the REPL <em>repeatedly<\/em>.<\/p>","summary":"Today I learned how to do multiline input in the REPL using an uncommon combination of arguments for the built-in open.","date_modified":"2026-03-02T16:21:46+01:00","tags":["repl","programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/139.multiline-input-in-the-repl\/thumbnail.webp"}]}
