1
0
mirror of https://github.com/balkian/balkian.github.com.git synced 2025-09-17 07:12:20 +00:00

10 Commits

Author SHA1 Message Date
balkian
239bb9ee6b CI: Update theme 2025-09-11 01:11:57 +00:00
J. Fernando Sánchez
565a117ad0 hyperloglog 2025-09-09 20:24:12 +02:00
J. Fernando Sánchez
a6de3dbabd nixos 2025-08-31 20:03:27 +02:00
J. Fernando Sánchez
2b54cecdab add post about ESP32 issues 2025-07-20 03:17:22 +02:00
J. Fernando Sánchez
f5353dd193 post: rdf is dead 2025-03-07 10:42:06 +01:00
J. Fernando Sánchez
dc2e3dd59c refactor 2025-03-07 10:41:54 +01:00
J. Fernando Sánchez
3e3cb40067 add tags to searchable content 2025-03-06 11:53:05 +01:00
J. Fernando Sánchez
999c2c1287 efficient collab: extend sections 2025-03-06 08:54:42 +01:00
J. Fernando Sánchez
887cd28206 add mention to Oxide 2025-03-06 07:55:20 +01:00
J. Fernando Sánchez
9148dba92e remove resoures/_gen 2025-03-06 07:55:10 +01:00
55 changed files with 789 additions and 72 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ output
*.pid
*.pyc
_gen
*/_gen

View File

@@ -11,6 +11,8 @@ defaultContentLanguage = "en"
# This will make .Summary and .WordCount behave correctly for CJK languages.
hasCJKLanguage = false
enableEmoji = true
# Change it to your Disqus shortname before using
#disqusShortname = "hugo-theme-stack"

View File

@@ -1,6 +1,6 @@
# Related contents configuration
includeNewer = true
threshold = 60
threshold = 10
toLower = false
[[indices]]

View File

@@ -1,9 +1,10 @@
---
title: Cheatsheets
name: "cheats"
slug: cheatsheets
menu:
main:
weight: 4
weight: 5
params:
icon: infinity
---

View File

@@ -1,5 +1,6 @@
---
title: Linux
title: Linux Cheatsheet
slug: linux
author: "Fernando Sánchez"
description: Tips and tricks for GNU/Linux and Unix
image: "img/linux.png"

View File

@@ -36,3 +36,20 @@ From tqdm's github repository:
* Disk-space efficient, with a global cache for dependency deduplication.
* Installable without Rust or Python via curl or pip.
* Supports macOS, Linux, and Windows.
### [pipdeptree](https://pypi.org/project/pipdeptree/)
A tool to generate a dependency tree from a virtualenv.
Useful to generate a clean `requirements.txt` or to clean up one that was generated with `pip freeze`.
Usage:
```
$ pipdeptree --exclude pip,pipdeptree,setuptools,wheel --warn silence | grep -E '^\w+' | tee requirements-clean.txt
Flask==0.10.1
gnureadline==8.0.0
Lookupy==0.1
pipdeptree==2.0.0b1
setuptools==47.1.1
wheel==0.34.2
```

36
content/page/about.md Normal file
View File

@@ -0,0 +1,36 @@
---
title: About
slug: about
readingTime: false
comments: false
menu:
main:
weight: 99
params:
icon: user
---
Hello there, stranger! :wave:
## About me
My name is Fernando, and I like learning and solving hard problems.
Especially when it comes to computers, engineering and languages.
I currently work at the Technical University of Madrid (UPM) as an assistant professor in the school of Telecommunications Engineering.
You can check out my [previous projects](/page/projects), and [my publications](https://scholar.google.com/citations?user=JLNusZ8AAAAJ&hl=en).
Feel free to get in touch through the comment section, an e-mail (`my first initial` `@sanchezrada.es`) or any other platform.
I am always happy to help and collaborate.
## About this blog
I use this blog for future reference, to write down some of the lessons I learn so.
I also see it as an exercise in reflection and sorting out my ideas.
Although I mostly do this for myself, to keep some lasting notes for the future, I also do it in hopes it might help someone like me in the future.
Each post is an independent note.
To keep some structure I will try to stick to general categories (e.g., programming, project management, linux), and add meaningful tags to help you and me find this information in the future.
You may also use the [search bar](/search) if you are looking for something specific and wondering if I've covered it.
For short thematically connected snippets and tips, I keep a dedicated section with [Cheatsheets](/cheatsheet).

View File

@@ -5,7 +5,7 @@ layout: "archives"
slug: "archives"
menu:
main:
weight: 2
weight: 98
params:
icon: archives
---

View File

@@ -1,22 +0,0 @@
---
title: Emacs
description: Configuration files and tricks for emacs
image: "img/emacs.png"
tags:
- emacs
- org
- productivity
- lisp
---
## Show plain text version
<!--more-->
```lisp
(font-lock-mode)
```

View File

@@ -1,20 +0,0 @@
---
title: Raspberry Pi
description: Tools, links and configuration for your Raspberry Pi
image: img/rpi.png
tags:
- rpi
---
## HDMI flickering
Avoid HDMI flickering/intermittent blanking on RPI with a 1400x1050 VGA monitor.
```python
hdmi_drive=2
hdmi_group=2
hdmi_mode=42
disable_overscan=1
config_hdmi_boost=7
```

View File

@@ -1,14 +1,40 @@
---
title: Links
description: Some pointers to useful resources.
readingTime: false
links:
- title: GitHub
description: My GitHub profile
- title: My GitHub profile
website: https://github.com/balkian
image: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
- title: gitea
description: My gitea profile
- title: GSI's GitHub
description: I contribute to both public and private projects on the "Grupo de Sistemas Inteligentes" organization.
website: https://github.com/gsi-upm
image: "/img/gsi.png"
- title: Dotfiles
description: My configuration files.
website: https://github.com/balkian/dotfiles
image: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
- title: Gists
description: A collection of snippets that are/were useful for very specific tasks.
website: https://github.com/balkian/gists
image: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
- title: Oxide and friends
description: A weekly podcast about technology, from the guys behind the Oxide Computer Company.
website: https://oxide-and-friends.transistor.fm/
image: https://img.transistor.fm/e6CHJbwIxDiCUoVc9ylEKOnBKax85QPGwFI5Lezj2ho/rs:fill:800:800:1/q:60/aHR0cHM6Ly9pbWct/dXBsb2FkLXByb2R1/Y3Rpb24udHJhbnNp/c3Rvci5mbS9zaG93/LzI5MjU2LzE2NDg0/OTAxMDAtYXJ0d29y/ay5qcGc.webp
- title: The Changelog Podcast(s)
description: The Changelog podcast is software's best weekly news brief (Mondays), deep technical interviews (Wednesdays) & talk show (Fridays).
website: https://changelog.com
image: https://cdn.changelog.com/static/images/podcasts/podcast-medium-126fc11a345517eb5ae5708daee38390.png
- title: Two's complement
description: A podcast by Matt Godbolt and Ben Rady about programming.
website: https://www.twoscomplement.org
image: /img/twos-complement.png
- title: My self-hosted gitea instance
description: I use this for private projects, and to keep mirrors of important projects on GitHub, just in case.
website: https://git.sinpapel.es/balkian
image: https://git.sinpapel.es/assets/img/logo.svg
menu:
main:
weight: 4

View File

@@ -1,8 +1,9 @@
---
title: Projects
readingTime: false
menu:
main:
weight: 1
weight: 90
params:
icon: clock
---
@@ -24,6 +25,6 @@ Past Projects
* [Marl](http://gsi.dit.upm.es/ontologies/marl): I updated this ontology, originally created by Adam Westerski, to make it compatible with the W3C's provenance ontology.
* [Hermes](http://github.com/balkian/hermes): one of my first projects, developed together with David Pérez as the special custom assignment in one of our courses. Hermes is an affective bot designed to mimic the behavour of humans. It included a plug-in system for its sensors and actuators. The information from its sensors changed its emotional state, which was shown via its actuators. Among others, it could fetch inforation from Twitter or its host system and change the expressions of an external Face made with servo motors or speak via its Text-To-Speech software. For instance, it could detect it was running out of battery, showing a sad face and sending an alerting tweet. You can see it in action in these two youtube videos: [Part 1](http://www.youtube.com/watch?v=KnEYahPD9z4) and [Part 2](http://www.youtube.com/watch?v=lQZldCTPEJc).
* [Maia](http://github.com/gsi-upm/maia): the Modular Architecture for Intelligent Agents is an evented agent architecture that aims to update the classical frameworks for intelligent agents with the concepts emerged from the Live Web.
* [EESTEC.net](http://github.com/eestec/eestec.portal): the Plone based official portal of EESTEC. It has been my first and only experience with Plone. I fixed some bugs and implemented basic features.
* [EESTEC.net](http://github.com/eestec/eestec.portal): the Plone based official portal of EESTEC. I fixed some bugs and implemented basic features.
For more information, check my list of public repositories in <a href="http://github.com/balkian"><i class="fab fa-github"> Github</i></a>.

View File

@@ -7,7 +7,7 @@ outputs:
- json
menu:
main:
weight: 3
weight: 2
params:
icon: search
---

View File

@@ -0,0 +1,93 @@
---
title: "Installing Micropython on the Super Mini ESP32-S3"
description:
date: 2025-07-20T02:30:43+02:00
image:
math:
license:
hidden: false
comments: true
draft: false
---
## Context
Many years ago I bought a "bargain" bluetooth scale.
I wanted a way to automatically log my weight, and the Xiaomi equivalent was more than twice as expensive at the time.
The problem is that it came with a very basic and somewhat sketchy software that required signing up, and none of the typical apps (openscale, gadgetbridge...) supported it.
I looked at how to reverse engineer it, but I did not have much time back then, and I wrote it off as a "future project".
Luckily, it had a screen, so I've been using it as a regular scale.
I did not even bother to check the body composition metrics, because I got a very depressing reading of 35% body fat.
Flash forward to today, when I decided to finally work on it.
In addition to trying nrfConnect, I installed the app to see the actual values..
It turns out the composition data was completely off!!
When a reading starts, the app needs to tell the scale some basic data about you (age, height, sex).
Unfortunately, there is no way (that I know of) to set a default user, so when the phone is not connected, it goes back to the wildly inaccurate reading.
So, I decided to connect one of my idle ESP32 boards to it, to interface with the scale through bluetooth (sending the right user data) and logging the results somewhere through WiFi.
## The problem
Installing the firmware was not too difficult, after I got the board to properly register.
```
uv tool run esptool.py --port /dev/ttyACM4 erase-flash
...
Chip type: ESP32-S3 (QFN56) (revision v0.2)
Features: Wi-Fi, BT 5 (LE), Dual Core + LP Core, 240MHz, Embedded Flash 4MB (XMC), Embedded PSRAM 2MB (AP_3v3)
...
uv tool run esptool.py --port /dev/ttyACM4 --baud 460800 write_flash 0 ESP32_GENERIC_S3-20250415-v1.25.0.bin
```
But then, I kept getting this error on boot: `OSError: (-24579, 'ESP_ERR_FLASH_NOT_INITIALISED')`.
And I could not copy any files or install any packages.
After some research, I narrowed the possibilities to:
* A wrong partition table, most likely due to the fact that my board only has 4MB of flash.
* Using an unsupported type of PSRAM (less likely)
## The solution
At this point, I could either try to compile the firmware with a custom partition (kind of tedious), or I could use the wonderful [mp-image-tool-esp32](https://github.com/glenn20/mp-image-tool-esp32) tool to check existing firmwares and modify partition sizes.
I confirmed my suspicion:
```
uv tool run mp-image-tool-esp32 ~/Downloads/ESP32_GENERIC_S3-20250415-v1.25.0.bin
Running mp-image-tool-esp32 v0.1.1 (Python 3.12.11).
Opening /home/j/Downloads/ESP32_GENERIC_S3-20250415-v1.25.0.bin...
Found esp32s3 firmware file (8MB flash).
Partition table (flash size: 8MB):
╭──────────┬──────┬─────────┬──────────┬──────────┬──────────┬───────┬───────────╮
│ Name │ Type │ SubType │ Offset │ Size │ End │ Flags │ │
├──────────┼──────┼─────────┼──────────┼──────────┼──────────┼───────┼───────────┤
│ nvs │ data │ nvs │ 0x9000 │ 0x6000 │ 0xf000 │ 0x0 │ (24.0 kB) │
│ phy_init │ data │ phy │ 0xf000 │ 0x1000 │ 0x10000 │ 0x0 │ (4.0 kB) │
│ factory │ app │ factory │ 0x10000 │ 0x1f0000 │ 0x200000 │ 0x0 │ (1.9 MB) │
│ vfs │ data │ fat │ 0x200000 │ 0x600000 │ 0x800000 │ 0x0 │ (6.0 MB) │
╰──────────┴──────┴─────────┴──────────┴──────────┴──────────┴───────┴───────────╯
Micropython app fills 79.3% of factory partition (410 kB free)
```
```
uv tool run mp-image-tool-esp32 -f 4MB --resize vfs=2MB ESP32_GENERIC_S3-20250415-v1.25.0.bin
uv tool run esptool.py --port /dev/ttyACM4 --baud 460800 write_flash 0 ESP32_GENERIC_S3-20250415-v1.25.0-4MB-resize=vfs=2097152.bin
```
After that, I was able to install new packages with `mpremote` and `mip`:
```
uv tool run mpremote mip install aioble
```
We can check that we are fully utilizing the 2MB of PSRAM:
```
uv tool run mpremote exec 'import gc;print(gc.mem_free(), "bytes")'
2061456 bytes
```
If you see much less than that, you are probably using the wrong variant (try `SPIRAM_OCT`).

View File

@@ -170,8 +170,6 @@ I'd argue that the path to successfully managing a research team lies in roughly
* Fostering autonomy
* Avoiding miscommunication
* Optimizing your contribution
* Coordinating with other teams
* Creating a team
The remaining of the post will be a series of tasks or rules to achieve these goals.
@@ -257,6 +255,10 @@ Whenever a member asks you something useful that is not documented, don't just a
Take the time to add this information yourself (e.g., by copy-pasting your response) or task that member with expanding the documentation themselves once they find an answer.
If your organization's culture does not encourage using these docs, they will quickly get outdated and fall out of use.
One example of taking this documentation approach really seriously is [Oxide (computer company)](https://oxide.computer/).
They have a process they call [Request For Discussion (RFD)](https://rfd.shared.oxide.computer/), which they use to discuss and document both technical and organizational decisions.
For instance, they have [RFDs on why they record every meeting](https://rfd.shared.oxide.computer/rfd/0537), [RFDs about their choice of database](https://rfd.shared.oxide.computer/rfd/0110), and even [an meta-RFD that discusses the motivation RFDs and how the process should work](https://rfd.shared.oxide.computer/rfd/0001).
#### Trust your teammate's ability to learn
@@ -266,6 +268,49 @@ What they lack in experience, they make up for with free time, a (more) neuropla
Sure, they will make mistakes (see the next section) and need some feedback (two sections above), but that is how we all learnt.
#### Use tools wisely
Your students probably have little experience with code versioning, reviewing processes, time management, etc.
A good choice of tools and some training can go a long way and make your life much easier in the long run.
It will also give your students a taste of what working in a bigger/real company feels like and a head start.
For instance, using git makes it easier to collaborate on code.
It also ensures that your results will not be lost if your student's laptop gets stolen.
Using GitLab CI or GitHub Actions to deploy public services will provide more autonomy to your students.
It will force them to commit working code, and it will make it easier to check their results and discuss the end result.
Using overleaf for theses has most of the advantages for collaboration as something like google docs, while being much more flexible and easier to produce formatting results.
You may also use something like latex on a shared folder (e.g., nextcloud), although the chances of connflicts is higher, so be careful with documents that require live collaboration.
In both cases, make sure to make the getting started experience as simple as possible: provide a sensible template, and only focus on simple features at first.
Also, on a related note, make sure every team member has a proper development setup.
It does not matter which tool they use (VSCode, emacs, Jetbrains), as long as they are comfortable with it and they are able to focus on actual work.
It helps to have a sensible default for your organization that is easy to set up and use, especially because most students do not have enough experience or skill with any particular tool.
#### Encourage cooperation
Do not become the center of every conversation.
If a topic can be discussed between two students, let them handle it on their own and get back to you if they need anything.
The ability to discuss with your peers and report only when needed will be extremely important for them in the future.
They are also likely to discuss the topic more openly and more relaxed thhan with you (no matter how approachable you are).
That might lead to valuable insights and improvements for your team and project.
Moreover, this attitude of open collaboration will help create those synergies we mentioned before, and make future projects easier and more enjoyable.
#### Reward proactivity
The whole point of this section is to get your team to work independently when possible.
Be explicit about this goal to make sure it is clear to everyone.
And encourage behavior that aligns with this goal, even on a small scale.
For instance, show interest when a student has shown initiative and researched something on their own, or when they go beyond the minimum requirements.
Sometimes, you will notice that this research was not completely well oriented or it was not a very efficient use of time.
Do not jump straight to criticize it.
Compliment the attitude regardless, try to find the value in the results, and be gentle when providing feedback on why other topics or tasks were higher priority or a better choice.
#### Don't be a perfectionist
Perfect is the enemy of done.
@@ -288,7 +333,7 @@ These are some tips to help keep everyone on the team informed and aligned.
All team members should understand the general priorities (project-wise) as well as the specific prorities of their assigned tasks.
This will help inform their decisions when some other tasks inevitably come up, or the urgency of a task changes.
#### Define boundaries
#### Define boundaries (and abstractions)
Once again, the goal is generally to achieve some sort of parallelism between your team members.
In order to do that, they need to know how they will interact with each other.
@@ -302,10 +347,15 @@ This often takes the form of an API, a file with a given format, or a section of
Take some time to define the boundary as precisely as needed at that point in the project.
I would suggest having specific examples that you can discuss and modify.
It is hard to discuss in the abstract, especially for inexperienced contributors.
When in doubt, default to the simplest option (e.g., a common file vs using a database).
Do not dwell too much on specific structural/representation details (e.g., which OWL vocabulary to use), but make sure that all the necessary bits are there.
Converting a document or querying a document store (e.g., elasticsearch) instead of your file system is relatively easy, but making up non-existing data can be a challenge.
One type of failure I've seen quite frequently in this area is to be too fuzzy about the expected results from a team (or contributor), and refusing to discuss or provide examples.
That tends to result in multiple iterations, each of them not-quite-what-you-wanted, and frustration in both sides.
#### Be approachable
Did I wrote a whole section about autonomy? Yes.
@@ -385,3 +435,31 @@ Even worse, our attention span and memory are finite, so longer and dense meetin
For these reasons, be very clear about these time limits, and do not extend these meetings unless it is strictly necessary.
You can always schedule a new meeting, but be sure to provide enough time in between to process the results of the meeting, reflect and prioritize.
## Beyond your team
The previous points and rules focus mostly on actions that can be applied within your team, and that you can fully control.
But teams rarely work in isolation, you will most likely
In order to be effective, you also need to coordinate with other teams/groups, and more generally work on your organization's culture and sense of belonging.
Many of the aspects I talked about in the team section apply here.
For instance, the obsession with documentation can - and should - be applied organization-wise.
The same goes for defining boundaries and using concrete examples when collaborating with other teams.
For most intents and purposes, you can treat other teams as another contributor to your team.
Just one that will be more costly and slow to interact with.
If possible, I'd try to apply the rule about focusing on the big picture, and limit most meetings to those that strictly need to be involved.
Avoid involving whole teams in discussions when the broad strokes have not been defined yet.
The responsibilities will be dilluted in a bigger group, it will be harder to avoid misunderstandings and easier to bikeshed.
On the organization's side, I would suggest having an honest conversation about your core principles.
I really liked [Bryan Cantrill's talk about principles of technology leadership](https://www.youtube.com/watch?v=9QMGAtxUlAchttps://www.youtube.com/watch?v=9QMGAtxUlAc).
He goes deep into the effects that principles have had on well known companies, and how to go about defining your company's principles.
I think that writing down your principles forces you to be conscious about their trade-offs, and to be explicit about your choices and attitudes.
More generally, try to define (light) processes that reward and facilitate behaviors you find positive, such as writing documentation and being proactive.
And try to discourage the opposite type of behavior as soon as possible, to make correcting them easier.
Apply the ideas of frequent evaluation and feedback, openness and honesty in every aspect of your organization.

View File

@@ -0,0 +1,18 @@
---
title: "Emacs: show plain text version"
#image: "img/emacs.png"
tags:
- emacs
- org
- productivity
- lisp
- snippet
---
<!--more-->
```lisp
(font-lock-mode)
```

View File

@@ -0,0 +1,101 @@
---
title: "NixOS and Home Manager"
description:
date: 2025-08-31T18:53:25+02:00
image: /img/nixos.svg
math:
license:
hidden: false
comments: true
draft: false
---
Nix is a declarative package manager (and its main language) that allows for reproducible environments (i.e., files in a folder).
One of the cool things about nix is that you can use it to run a package without installing it permanently in your system.
For instance, I recently needed the `readelf` utility and it wasn't installed in my system, so I ran:
```
nix run 'nixpkgs#toybox' -- readelf -l /home/j/.local/share/uv/python/cpython-3.10.11-linux-x86_64-gnu/bin/python | grep interpreter
```
That's it.
No need to deal with dependencies, incompatibilities, or to uninstall the package once you no longer need it.
NixOS is a Linux Operating system that uses this package manager and system configuration (e.g., users, network interfaces, etc.) defined in the Nix language to create reproducible systems.
Home-manager takes the concepts from NixOS to user-space, allowing you to define your personal environment (i.e., home).
It can be used on any system that supports the Nix package manager (e.g., Linux, Mac and Windows).
There is a lot to like
## Context
(Last year)[https://github.com/balkian/dotfiles/commit/fa7041ff8bde6c5288ad55db6b0d61ed593bbccc] I started experimenting with nix and home-manager to configure my environments.
The context is that for some years now I've been switching devices quite frequently.
It started when my main laptop died (DELL!!), I started using a desktop for most of my work, but I also needed a laptop to teach.
One thing led to another, and I ended up using vanilla Ubuntu or EndeavourOS installations with Gnome and very minimal customization.
That in itself is not wrong.
The thing is that, before that, I was a [tinkerer with a customized environment](https://github.com/balkian/dotfiles).
1. I was being inefficient (and I felt it)
2. That inefficiency turned some easy things into a chore
3. This made work in general less enjoyable.
3. As a result, I became even less productive
The idea was to get back to a usable terminal and editor setup with minimal
## NixOS
I've only been running NixOS for three days now, but so far I like the experience.
My approach is to keep the system definition as simple as possible, and rely on home-manager to install and configure most of my tools.
The basics would be:
* Network configuration
* System users
* Desktop environment (wayland and multiple DE's)
All that goins into my `/etc/nixos/configuration.nix`, and then I do:
```
sudo nixos-rebuild switch
```
If it fails, nothing is changed in your system.
If it works, most of the changes will have been applied (e.g., new services started, others stopped).
The previous configuration does not disappear: you can roll back to it manually or when booting your system.
## Home-Manager
Here's where most of my configuration lives.
I use `homeConfigurations` to keep more than one configuration in the same repo (e.g., work (`j@lenny`) and personal computers (`j`, on any other system)).
To make it easier to install this configuration
### Running on NixOS
There are two ways to run it: as part of your general configuration (i.e., it will be applied using the same `nixos-rebuild` command) or standalone.
I prefer the latter, because it allows me to tinker with the configuration more easily, and it is no different from applying it on other systems (see below).
After you change your config, simply run:
```
home-manager switch
```
### On other systems
Running nix on other operating systems (e.g., Arch Linux) is similar to a standalone installation on NixOS.
The only difference is that you need to [install nix first](https://nix.dev/install-nix.html) and [configure the appropriate channels](https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone).
## It's a little flakey
Flakes are an experimental feature of nix that use version control and some special conventions to bring more reproducibility guarantees.
For instance, they make it trivial to install a specific commit version of my home-manager config, which in itself could use program definitions from external git repositories.
Flakes are not the only solution to this problem, and they are a bit controversial among hardcore nix users.
However, the community as a whole seems to have embraced flakes.
I personally chose to set up my config as a flake.
I still have not tried to install it on other systems, so time will tell.

232
content/post/hyperloglog.md Normal file
View File

@@ -0,0 +1,232 @@
---
title: "HyperLogLog: magical (kind of) unique counters"
description:
date: 2025-09-08T20:08:22+02:00
image:
math:
slug: hyperloglog
license:
hidden: false
comments: true
draft: false
---
> **TL;DR**
> [HyperLogLogs](https://en.wikipedia.org/wiki/HyperLogLog) (hll) can be a replacement for a set when only the number of unique elements (cardinality) is needed, **and an approximation is enough**.
>
> It is a very efficient structure both in time (`add` is `O(1)`) and space (fixed size typically ~12KB, depending on the desired error rate).
>
> Moreover, two hll can be **merged very efficiently ($O(1)$)**, with no loss of precision.
## The problem
### A simplification
Imagine you are a store owner, and you want to count how many unique customers you get each day.
Simple enough, you only need some pen and a piece of paper.
Every time a customer walks in, you check whether their name is already on your paper[^1].
If it is, you don't need to do anything.
If it isn't, you add it.
[^1] You're a great store keeper, so you know every single customer by name!
At the end of the day, you simply count the number of names in your paper to get your answer.
One problem you may find is that checking whether a customer is already in the list can be slow, and you may not be able to keep up with the ludicrous thoroughput of customers to your successful store.
There are some tricks to alleviate this:
* Sorting. Keeping your list of names always sorted. This can be very difficult with pen and paper :)
* Partitioning. Separating your clients by some criteria (e.g., first letter in their surname) and assigning a portion of your paper for each possible value. You may repeat this process for each portion.
* Deferring deduplication. Write their names every time, and wait until a later time to check if you have already seen them. That later time can be a less busy moment of the day, or some time after the day is over.
The second problem is that the list of users
In a programming context, the first problem translate to time complexity.
The typical approach is to have something like a a hash set.
That makes checking very fast ($O(1)$).
Adding a new name is also fast, unless the set needs to grow (amortized $O(1)$).
A drawback is that hash sets need to be reasonably empty to work as intended, so we are trading speed for space.
The second problem translates to memory utilization.
If each name has a length of $l$ bytes, and we have a total number of $u$ unique users, we would need **at least** $l \times u$ bytes, so $O(l \times u)$.
For simplification, we can express $u$ as a product of how many times customers enter the store ($n$) and the ratio ($r$) of those visits that are from a unique customer: $u = n * r$.
### The real problem
You are such a great owner that your business has grown and you now have ten ($10$) more stores in town.
Naturally, you want to see how each of the ten locations is doing, and you want to keep track of how unique customers your business has as a whole.
So you need to:
- Keep a record of each individual store. In our case, that would mean $10$ times more total records.
- Somehow merge the records from each location into one. If we have two counters, we need to go through all the elements of the smallest one, and add them to the bigger one. (plus optionally copying the biggest counter or starting anew and adding both to avoid modifying it)
This compounds the problems from the previous section:
- Each store does roughly the same, so the time and space needed grow $s$ times (for $s$ stores).
- To merge the counters, we roughly need to go through each unique customer of each store.
- We need to store the result. Depending on how many repetitions from store to store you get, this can go from as big as your biggest counter (all others are contained), up to the sum of all the counters (no repetition).
So, in summary, assuming we can insert with $O(1)$ and we get on average $n$ customers per store, we need roughly:
- $O(s \times n)$ operations to get unique customers for every store.
- $O(s \times n \times r)$ operations to join them.
- $O((s+1) \times n \times r \times l)$ total space to save all of this information.
The two key factores here are that:
- Space grows linearly with the number of unique customers $n \times r$
- Space grows lineraly with the number of stores ($s$)
- Space grows linearly with the size of the key we are storing ($l$, length of names)
- Merging time grows linearly with the number of stores ($s$)
- Merging time grows linearly with the number of members in each counter ($n \times r$)
## Enter HyperLogLog
Let's look at a simpler problem before we explain the hyperlog.
Imagine you toss a coin until you get tails (or up to 100 throws).
How likely are you to draw a sequence of at least 10 heads?
For a fair coin, the probability of that event would be $1/2^{10}$
And 5 heads?
$1/2^5$.
And, in general, $1/2^h$, for $h$ heads.
We can flip the problem on its head (pun intended).
Let's say I repeat the same coin tossing multiple times, each time stopping when I get tails.
If I tell you I got **at most** 10 heads and tell you to guess **how many tails I got** (i.e., total sequences), what would you say?
A naive answer would be that, in order to get an event with $1/2^10$ chance, I probably did about $2^10$ attempts.
Much fewer or much more, and I would have gotten a different number.
> [!Warning]
> This part is based on my (limited) understanding of the math behind HLL.
> Please, take it with a grain of salt, and feel free to correct me.
The problem with this approach is that it produces very rough estimates (see the [FlajoletMartin algorithm](https://en.wikipedia.org/wiki/Flajolet%E2%80%93Martin_algorithm), especially for higher values of $h$.
The key idea behind HyperLogLog is that, instead of keeping track of a single number, we separate our attempts into multiple groups (registers).
Say, $m$ registers (for maxima).
The first attempt affects the first register, the second the second one, and so on, until we run out of registers and we start again.
Then, we get $m$ estimates of $n\_attempts / m$.
It turns out using the harmonic mean of all the attempts and multiplying it by $m$ is a great estimate of the total number!
Could we use the same idea to solve our original problem?
We only need a way to transform our input (the names) into a set of tosses.
The output has to be deterministic (to work as a unique detector), uniform (assumption).
But that is precisely the definition of a hashing function!
It turns an input into a series of ones and zeros of a fixed length.
And the distribution of values of a good hashing function should be uniform in order to minimize collisions.
We can turn a hash into a sequence of tosses by keeping only the leading part up to the first 1.
Actually, another practical difference is that, instead of doing a round robin selection of registers, we will use part of the hash to select which register to update.
The distribution of values should still be uniform, and we eliminate the need to keep a counter.
So, we use just enough bits from the beginning to select the register ($log_2(m)$ for $m$ registers), and we convert the rest into a sequence.
It turns out that counting the number of leading zeros (most significant bit) is very fast on modern computers due to hardware support.
And, no, that is not a coincidence.
In practical terms, we also need to apply some correction correct for hashing collisions.
Another practical difference is that there are hashing collisions. between and multiple hashes that start with the same sequence of zeros (and a one).
That means that, a HLL has $m$ registers (maxima), and for each element that is added it:
- Calculates a hash of each element (to get a uniform distribution and a fixed size)
- Assigns the element to one of the $R$ registers, $j$, based on the head of that hash (first $log_2(R)$ bits).
- Counts $z$, the number of leading zeros in the tail of the hash
- Increments the maximum in that register ($M[j] = max(z, M[j])$
To get an estimate of the cardinality we have three situations:
- The HLL does not have enough elements: $V > 0$, where $V$ is the number of zero elements. We use linear Counting ($E = m \times log( m / V))$)
- The HLL has enough elements. We estimate the cardinality $E$ as: $E = m \times harmonic\_mean(M) \times c$. Where $c$ is a factor that corrects for hash collisions.
- The HLL has too many elements. In this case, we need to correct the estimate from the second case.
The error of our estimation will be a function of the number of registers: $sigma = 1.04 / sqrt(m)$.
To merge two HLLs with the same number of registers and hashing functions, you only need to compare each register position, and get the maximum of the two values.
This operation is very fast ($O(m)$, which is small in practice).
There is also no loss of relative precision.
Going back to our original problem, we will analyze the effect of using HLLs to estimate each the number of unique customers in each store.
If we assume our hashing is $O(1)$ and we get on average $n$ customers per store, we get roughly:
- $O(s \times n)$ operations to get each store's unique customers
- $O(s \times m)$ operations to join them. Where $m$ is the number of registers in each HLL.
- $O((s+1) \times m)$ total space to save all of this information.
Hence, compared to our solution using sets:
- **Space ~grows linearly with~ does not depend on the number of unique customers**
- Space grows lineraly with the number of stores ($m$)
- **Space ~grows linearly with~ does not depend on the size of the key we are storing ($l$)**
- Space depends linearly with the number of buckets, which is a function of the precision/error rate
- Merging time grows linearly with the number of stores ($m$)
- **Merging time ~grows linearly with~ does not depend on the number of members in each counter ($n$)**
## Drawbacks
As magical as HLLs are, there are some caveats:
- The cardinality provided is an **estimation** up to an **error_rate**, usually in the rage of 1-2%
- In order to merge two HLLs, they need to have:
- The same number of registers
- The same hash function
- HyperLogLogs are append-only. You cannot remove elements
- You cannot calculate the difference of two HLLs (but you can calculate its cardinality)
## Additional properties
- The result of merging two HLLs has the same **error_rate**
- It is possible to calculate the cardinality of:
- the difference of two HLLs ($cardinality(a - b) = cardinality(a b) - cardinality(b)$)
- the intersection of two HLLs ($cardinality(a ∩ b) = cardinality(a) + cardinality(b) - cardinality(a b)$)
## SlidingHyperLogLog
There are some really neat variations over HLLs for specific use cases.
One I am specially interested in is the SlidingHyperLogLog, which can calculate the cardinality of events over a rolling window.
An application of this would be to keep counters of unique IPs seen on a network over the past $W$ minutes.
Since HLL are only additive, it is not possible to apply the typical approach in sliding windows of removing old elements that are no longer within the window.
The naive solution to get a Sliding Window counter of width $W$ ould be be to get all events that happened in the past $W$ minutes and construct a HLL with them.
You would need to do that every $T$ minutes or for every incoming event, depending on your strategy/needs.
That is unnecessarily wasteful.
Imagine having a window of $W=120$ minutes with a resolution of $T=1$ minute.
Instead, the SlidingHyperLogLog exploits the fact that HLLs only keep track of the maximum count in each register.
The intuition is that, when we add an element at time $t$ with count $V$ and belonging to register $r$, we can:
- Forget any elements in register $r$ that:
- Were added before $t-W$
- Had a count $<=V$
- Add this event and time ($(t, V)$) to a list of values for the register
- Update the count in the register to the maximum of all the remaining elements
Using some clever data structures (e.g., a binary heap) and algorithms, we can very efficiently perform the insertion and removal of events.
## Python
For now, I've only used the [HyperLogLog](https://github.com/svpcom/hyperloglog/) library, which is a python implementation that relies on $numpy$:
```python
!pip install hyperloglog
from hyperloglog import HyperLogLog
h = HyperLogLog(error_rate=0.01)
h.add("Hello from my blog")
print(len(h))
```

View File

@@ -0,0 +1,29 @@
---
title: "RDF Is Dead"
description:
date: 2025-03-07T10:24:52+01:00
image:
math:
tags:
- semantic web
license:
hidden: false
comments: true
draft: false
---
A big part of my research has been around vocabularies and semantic annotation.
And, to be honest, I've grown increasingly dissatisfied with the field.
To the point where I dread having to work on it.
Some day I will write about it in length, but today I've stumbled upon a post that covers the topic quite well: [The Semantic Web is Dead - ~~Long Live the Semantic Web~~](https://terminusdb.com/blog/the-semantic-web-is-dead/) (styling mine).
In particular, this section has really resonated with me:
># Academics and Industry
>
>The political economy of academia and its interaction with industry is the origin of our current lack of a functional Semantic Web.
>
>Academia is structured in a way that there is very little incentive for anyone to build usable software. Instead, you are elevated for rapidly throwing together an idea, a tiny proof of concept, and to iterate on microscopic variations of this thing to produce as many papers as possible.
>
> In engineering, the devil is in the detail. You really need to get into the weeds before you can know what the right thing to do is. This is simultaneously a devastating situation for industry and academia. Nobody is going to wait around for a team of engineers to finish building a system to write about it in Academia. Youll be passed immediately by legions of paper pushers. And in industry, you cant just be mucking about with a system that you might have to throw away.
{.note}

View File

@@ -0,0 +1,21 @@
---
title: Fixing HDMI flickering
image: img/rpi.png
readingTime: false
categories:
- Linux
tags:
- rpi
- snippet
---
Use this config to avoid HDMI flickering/intermittent blanking on RPI with a 1400x1050 VGA monitor.
```python
hdmi_drive=2
hdmi_group=2
hdmi_mode=42
disable_overscan=1
config_hdmi_boost=7
```

2
go.mod
View File

@@ -2,4 +2,4 @@ module github.com/CaiJimmy/hugo-theme-stack-starter
go 1.17
require github.com/CaiJimmy/hugo-theme-stack/v3 v3.30.0 // indirect
require github.com/CaiJimmy/hugo-theme-stack/v3 v3.31.0 // indirect

4
go.sum
View File

@@ -1,2 +1,2 @@
github.com/CaiJimmy/hugo-theme-stack/v3 v3.30.0 h1:uITC7EKGyfPjyi3C5At++E0Uu1qQXtqiwMV4pd7LkLs=
github.com/CaiJimmy/hugo-theme-stack/v3 v3.30.0/go.mod h1:IPmCXiIxlFSLFYS0tOmYP6ySLviyeNVSabyvSuaxD+I=
github.com/CaiJimmy/hugo-theme-stack/v3 v3.31.0 h1:fDdsTsOVVKBzQb9+hIxTErDqmiHCzcTD2Bd+7Se0TlA=
github.com/CaiJimmy/hugo-theme-stack/v3 v3.31.0/go.mod h1:IPmCXiIxlFSLFYS0tOmYP6ySLviyeNVSabyvSuaxD+I=

BIN
gsi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

26
layouts/page/search.json Normal file
View File

@@ -0,0 +1,26 @@
{{- $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}}
{{- $notHidden := where .Site.RegularPages "Params.hidden" "!=" true -}}
{{- $filtered := ($pages | intersect $notHidden) -}}
{{- $result := slice -}}
{{- range $filtered -}}
{{- $data := dict "title" .Title "date" .Date "permalink" .Permalink "content" (print .Plain "Tags: " (.Params.tags)) -}}
{{- $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" -}}
{{- if $image.exists -}}
{{- $imagePermalink := "" -}}
{{- if and $image.resource (default true .Page.Site.Params.imageProcessing.cover.enabled) -}}
{{- $thumbnail := $image.resource.Fill "120x120" -}}
{{- $imagePermalink = (absURL $thumbnail.Permalink) -}}
{{- else -}}
{{- $imagePermalink = $image.permalink -}}
{{- end -}}
{{- $data = merge $data (dict "image" (absURL $imagePermalink)) -}}
{{- end -}}
{{- $result = $result | append $data -}}
{{- end -}}
{{ jsonify $result }}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"Target":"/scss/style.min.663803bebe609202d5b39d848f2d7c2dc8b598a2d879efa079fa88893d29c49c.css","MediaType":"text/css","Data":{"Integrity":"sha256-ZjgDvr5gkgLVs52Ejy18Lci1mKLYee+gefqIiT0pxJw="}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

87
static/img/nixos.svg Normal file
View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="435.58978mm" height="136.68491mm" viewBox="0 0 1543.4284 484.31659" id="svg2" version="1.1" inkscape:version="0.92.0 r15299" sodipodi:docname="nixos-hex.svg">
<defs id="defs4">
<linearGradient inkscape:collect="always" id="linearGradient5562">
<stop style="stop-color:#699ad7;stop-opacity:1" offset="0" id="stop5564"/>
<stop id="stop5566" offset="0.24345198" style="stop-color:#7eb1dd;stop-opacity:1"/>
<stop style="stop-color:#7ebae4;stop-opacity:1" offset="1" id="stop5568"/>
</linearGradient>
<linearGradient inkscape:collect="always" id="linearGradient5053">
<stop style="stop-color:#415e9a;stop-opacity:1" offset="0" id="stop5055"/>
<stop id="stop5057" offset="0.23168644" style="stop-color:#4a6baf;stop-opacity:1"/>
<stop style="stop-color:#5277c3;stop-opacity:1" offset="1" id="stop5059"/>
</linearGradient>
<linearGradient id="linearGradient5960" inkscape:collect="always">
<stop id="stop5962" offset="0" style="stop-color:#637ddf;stop-opacity:1"/>
<stop style="stop-color:#649afa;stop-opacity:1" offset="0.23168644" id="stop5964"/>
<stop id="stop5966" offset="1" style="stop-color:#719efa;stop-opacity:1"/>
</linearGradient>
<linearGradient y2="515.97058" x2="282.26105" y1="338.62445" x1="213.95642" gradientTransform="translate(983.36076,601.38885)" gradientUnits="userSpaceOnUse" id="linearGradient5855" xlink:href="#linearGradient5960" inkscape:collect="always"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient5562" id="linearGradient5384" gradientUnits="userSpaceOnUse" gradientTransform="translate(70.650339,-1055.1511)" x1="200.59668" y1="351.41116" x2="290.08701" y2="506.18814"/>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient5053" id="linearGradient5386" gradientUnits="userSpaceOnUse" gradientTransform="translate(864.69589,-1491.3405)" x1="-584.19934" y1="782.33563" x2="-496.29703" y2="937.71399"/>
</defs>
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.34760742" inkscape:cx="803.54996" inkscape:cy="186.45699" inkscape:document-units="px" inkscape:current-layer="g5329" showgrid="false" inkscape:window-width="1366" inkscape:window-height="706" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:snap-global="true" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0"/>
<metadata id="metadata7">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g inkscape:groupmode="layer" id="layer7" inkscape:label="bg" style="display:none">
<rect transform="translate(-132.5822,958.04022)" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="rect5389" width="1543.4283" height="483.7439" x="132.5822" y="-957.77832"/>
</g>
<g inkscape:groupmode="layer" id="layer5" inkscape:label="guide" style="display:none;opacity:0.51599995" transform="translate(-132.5822,958.04022)">
<rect y="-957.77832" x="132.5822" height="483.7439" width="1543.4283" id="rect5350" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#d4d4d4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<rect style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#9b9b9b;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="rect5346" width="1496.443" height="435.68069" x="155.77646" y="-933.38721" inkscape:export-xdpi="17.971878" inkscape:export-ydpi="17.971878"/>
<rect y="-851.65918" x="159.02695" height="272.58423" width="1492.5731" id="rect5348" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#848484;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
</g>
<g inkscape:groupmode="layer" id="layer6" inkscape:label="logo-guide" style="display:none" transform="translate(-132.5822,958.04022)">
<rect y="-958.02759" x="132.65129" height="484.30399" width="550.41602" id="rect5379" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5c201e;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" inkscape:export-filename="/home/tim/dev/nix/homepage/logo/nix-wiki.png" inkscape:export-xdpi="22.07" inkscape:export-ydpi="22.07"/>
<rect style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c24a46;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="rect5372" width="501.94415" height="434.30405" x="156.12303" y="-933.02759" inkscape:export-filename="/home/tim/dev/nix/homepage/logo/nixos-logo-only-hires-print.png" inkscape:export-xdpi="212.2" inkscape:export-ydpi="212.2"/>
<rect style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#d98d8a;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="rect5381" width="24.939611" height="24.939611" x="658.02826" y="-958.04022"/>
</g>
<g inkscape:label="print-logo" inkscape:groupmode="layer" id="layer1" style="display:inline" sodipodi:insensitive="true" transform="translate(-132.5822,958.04022)">
<path style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5277c3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 309.40365,-710.2521 122.19683,211.6751 -56.15706,0.5268 -32.6236,-56.8692 -32.85645,56.5653 -27.90237,-0.011 -14.29086,-24.6896 46.81047,-80.4902 -33.22946,-57.8256 z" id="path4861" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccccccc"/>
<path style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#7ebae4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 353.50926,-797.4433 -122.21756,211.6631 -28.53477,-48.37 32.93839,-56.6875 -65.41521,-0.1719 -13.9414,-24.1698 14.23637,-24.721 93.11177,0.2939 33.46371,-57.6903 z" id="use4863" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccccccc"/>
<path style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#7ebae4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 362.88537,-628.243 244.41439,0.012 -27.62229,48.8968 -65.56199,-0.1817 32.55876,56.7371 -13.96098,24.1585 -28.52722,0.032 -46.3013,-80.7841 -66.69317,-0.1353 z" id="use4865" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccccccc"/>
<path style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#7ebae4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 505.14318,-720.9886 -122.19683,-211.6751 56.15706,-0.5268 32.6236,56.8692 32.85645,-56.5653 27.90237,0.011 14.29086,24.6896 -46.81047,80.4902 33.22946,57.8256 z" id="use4867" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccccccc"/>
<path sodipodi:nodetypes="cccccccccc" inkscape:connector-curvature="0" id="path4873" d="m 309.40365,-710.2521 122.19683,211.6751 -56.15706,0.5268 -32.6236,-56.8692 -32.85645,56.5653 -27.90237,-0.011 -14.29086,-24.6896 46.81047,-80.4902 -33.22946,-57.8256 z" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5277c3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path sodipodi:nodetypes="cccccccccc" inkscape:connector-curvature="0" id="use4875" d="m 451.3364,-803.53264 -244.4144,-0.012 27.62229,-48.89685 65.56199,0.18175 -32.55875,-56.73717 13.96097,-24.15851 28.52722,-0.0315 46.3013,80.78414 66.69317,0.13524 z" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5277c3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<path sodipodi:nodetypes="cccccccccc" inkscape:connector-curvature="0" id="use4877" d="m 460.87178,-633.8425 122.21757,-211.66304 28.53477,48.37003 -32.93839,56.68751 65.4152,0.1718 13.9414,24.1698 -14.23636,24.7211 -93.11177,-0.294 -33.46371,57.6904 z" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#5277c3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/>
<g id="layer2" inkscape:label="guides" style="display:none" transform="translate(72.039038,-1799.4476)">
<path d="M 460.60629,594.72881 209.74183,594.7288 84.309616,377.4738 209.74185,160.21882 l 250.86446,1e-5 125.43222,217.255 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="true" sodipodi:arg2="1.5707963" sodipodi:arg1="1.0471976" sodipodi:r2="217.25499" sodipodi:r1="250.86446" sodipodi:cy="377.47382" sodipodi:cx="335.17407" sodipodi:sides="6" id="path6032" style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.23600003;fill:#4e4d52;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" sodipodi:type="star"/>
<path transform="translate(0,-308.26772)" sodipodi:type="star" style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#4e4d52;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" id="path5875" sodipodi:sides="6" sodipodi:cx="335.17407" sodipodi:cy="685.74158" sodipodi:r1="100.83495" sodipodi:r2="87.32563" sodipodi:arg1="1.0471976" sodipodi:arg2="1.5707963" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 385.59154,773.06721 -100.83495,0 -50.41747,-87.32564 50.41748,-87.32563 100.83495,10e-6 50.41748,87.32563 z"/>
<path transform="translate(0,-308.26772)" sodipodi:nodetypes="ccccccccc" inkscape:connector-curvature="0" id="path5851" d="m 1216.5591,938.53395 123.0545,228.14035 -42.6807,-1.2616 -43.4823,-79.7725 -39.6506,80.3267 -32.6875,-19.7984 53.4737,-100.2848 -37.1157,-73.88955 z" style="fill:url(#linearGradient5855);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<rect style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.41499999;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#c53a3a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="rect5884" width="48.834862" height="226.22897" x="-34.74221" y="446.17056" transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)"/>
<path transform="translate(0,-308.26772)" sodipodi:type="star" style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.50899999;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="path3428" sodipodi:sides="6" sodipodi:cx="223.93674" sodipodi:cy="878.63831" sodipodi:r1="28.048939" sodipodi:r2="24.291094" sodipodi:arg1="0" sodipodi:arg2="0.52359878" inkscape:flatsided="true" inkscape:rounded="0" inkscape:randomized="0" d="m 251.98568,878.63831 -14.02447,24.29109 h -28.04894 l -14.02447,-24.29109 14.02447,-24.2911 h 28.04894 z"/>
<use x="0" y="0" xlink:href="#rect5884" id="use4252" transform="matrix(0.5,0.8660254,-0.8660254,0.5,558.02636,12.372992)" width="100%" height="100%"/>
<rect style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:0.6507937;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" id="rect4254" width="5.3947482" height="115.12564" x="545.71014" y="467.07007" transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,-308.26772)"/>
</g>
</g>
<g inkscape:groupmode="layer" id="layer3" inkscape:label="gradient-logo" style="display:inline;opacity:1" sodipodi:insensitive="true" transform="translate(-132.5822,958.04022)">
<path sodipodi:nodetypes="cccccccccc" inkscape:connector-curvature="0" id="path3336-6" d="m 309.54892,-710.38827 122.19683,211.67512 -56.15706,0.5268 -32.6236,-56.8692 -32.85645,56.5653 -27.90237,-0.011 -14.29086,-24.6896 46.81047,-80.4901 -33.22946,-57.8257 z" style="opacity:1;fill:url(#linearGradient5384);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<use height="100%" width="100%" transform="rotate(60,407.11155,-715.78724)" id="use3439-6" inkscape:transform-center-y="151.59082" inkscape:transform-center-x="124.43045" xlink:href="#path3336-6" y="0" x="0"/>
<use height="100%" width="100%" transform="rotate(-60,407.31177,-715.70016)" id="use3445-0" inkscape:transform-center-y="75.573958" inkscape:transform-center-x="-168.20651" xlink:href="#path3336-6" y="0" x="0"/>
<use height="100%" width="100%" transform="rotate(180,407.41868,-715.7565)" id="use3449-5" inkscape:transform-center-y="-139.94592" inkscape:transform-center-x="59.669705" xlink:href="#path3336-6" y="0" x="0"/>
<path style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient5386);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 309.54892,-710.38827 122.19683,211.67512 -56.15706,0.5268 -32.6236,-56.8692 -32.85645,56.5653 -27.90237,-0.011 -14.29086,-24.6896 46.81047,-80.4901 -33.22946,-57.8256 z" id="path4260-0" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccccccc"/>
<use height="100%" width="100%" transform="rotate(120,407.33916,-716.08356)" id="use4354-5" xlink:href="#path4260-0" y="0" x="0" style="display:inline"/>
<use height="100%" width="100%" transform="rotate(-120,407.28823,-715.86995)" id="use4362-2" xlink:href="#path4260-0" y="0" x="0" style="display:inline"/>
</g>
<g style="display:inline" inkscape:label="text-vegur" id="g5329" inkscape:groupmode="layer" transform="translate(-132.5822,958.04022)">
<g aria-label="Nix" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:395.09683228px;line-height:125%;font-family:Carlito;-inkscape-font-specification:Carlito;letter-spacing:0px;word-spacing:0px;display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" id="text5407">
<path d="m 969.15319,-847.11833 h -30.81755 v 139.86428 c 0,19.75484 0.79019,50.96749 1.97548,85.73601 h -1.18529 c -15.40877,-28.84207 -32.79303,-56.49884 -45.04104,-75.46349 l -96.79872,-150.1368 h -42.27536 v 267.87565 h 30.81755 v -139.86427 c 0,-19.75485 -0.79019,-56.89395 -1.97548,-91.26737 h 1.18529 c 22.91561,39.90478 36.34891,62.0302 48.99201,80.99485 l 96.79872,150.13679 h 38.32439 z" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Vegur;-inkscape-font-specification:Vegur" id="path4683"/>
<path d="m 1027.8251,-579.24268 h 33.1881 v -191.22686 h -33.1881 z m 16.594,-219.27874 c 11.4578,0 20.5451,-9.08722 20.5451,-20.54503 0,-11.45781 -9.0873,-20.54504 -20.5451,-20.54504 -11.4578,0 -20.545,9.08723 -20.545,20.54504 0,11.45781 9.0872,20.54503 20.545,20.54503 z" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Vegur;-inkscape-font-specification:Vegur" id="path4685"/>
<path d="m 1267.7785,-770.46954 h -37.9293 l -46.6214,70.32723 h -1.1853 l -45.0411,-70.32723 h -41.09 l 68.3517,93.24285 v 1.18529 l -70.7223,96.79872 h 37.9293 l 49.7822,-75.85859 h 1.1853 l 49.7822,75.85859 h 41.09 l -72.3027,-98.37911 v -1.18529 z" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Vegur;-inkscape-font-specification:Vegur" id="path4687"/>
</g>
<g aria-label="O" transform="scale(0.95067318,1.0518862)" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:367.48727417px;line-height:125%;font-family:Carlito;-inkscape-font-specification:Carlito;letter-spacing:0px;word-spacing:0px;display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" id="text5356">
<path d="m 1468.5915,-800.79725 c -66.1477,0 -120.5358,48.14083 -120.5358,128.25306 0,80.11223 54.3881,128.25306 120.5358,128.25306 66.1477,0 120.5359,-48.14083 120.5359,-128.25306 0,-80.11223 -54.3882,-128.25306 -120.5359,-128.25306 z m 0,24.98914 c 49.2433,0 86.727,36.74872 86.727,103.26392 0,66.5152 -37.4837,103.26392 -86.727,103.26392 -49.2433,0 -86.727,-36.74872 -86.727,-103.26392 0,-66.5152 37.4837,-103.26392 86.727,-103.26392 z" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Vegur;-inkscape-font-specification:Vegur" id="path4680"/>
</g>
<g aria-label="S" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:386.55480957px;line-height:125%;font-family:Carlito;-inkscape-font-specification:Carlito;letter-spacing:0px;word-spacing:0px;display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" id="text5364">
<path d="m 1523.761,-773.88643 c 0,37.10927 19.3277,57.21012 64.1681,75.37819 34.4034,13.91598 48.3193,26.28573 48.3193,51.79835 0,30.92438 -25.126,46.38657 -58.3697,46.38657 -17.395,0 -37.1093,-2.70588 -58.7564,-10.05042 l -3.479,26.67228 c 18.9412,6.95799 39.8152,9.66387 60.6891,9.66387 51.7984,0 95.0925,-26.28573 95.0925,-79.24374 0,-36.7227 -22.4202,-54.50422 -67.6471,-72.6723 -30.1512,-11.9832 -44.8403,-24.73951 -44.8403,-51.41179 0,-25.89917 22.4202,-40.2017 50.6387,-40.2017 16.6218,0 34.7899,4.2521 47.5462,9.27732 l 3.479,-26.28573 c -14.6891,-6.18488 -32.8572,-9.27732 -52.958,-9.27732 -47.5463,0 -83.8824,27.4454 -83.8824,69.96642 z" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Vegur;-inkscape-font-specification:Vegur" id="path4677"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 25 KiB