<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>vorpal.se</title><link href="https://vorpal.se/" rel="alternate"></link><link href="https://vorpal.se/feeds/all.atom.xml" rel="self"></link><id>https://vorpal.se/</id><updated>2025-06-23T00:00:00+02:00</updated><entry><title>3D printing with unconventional vase mode</title><link href="https://vorpal.se/posts/2025/jun/23/3d-printing-with-unconventional-vase-mode/" rel="alternate"></link><published>2025-06-23T00:00:00+02:00</published><updated>2025-06-23T00:00:00+02:00</updated><author><name>Arvid Norlander</name></author><id>tag:vorpal.se,2025-06-23:/posts/2025/jun/23/3d-printing-with-unconventional-vase-mode/</id><summary type="html">&lt;p&gt;This article targets an advanced audience who are already familiar with
the 3D printing. In this article I will try to collect some information I haven&amp;rsquo;t
found written down in a single place yet. In particular, a lot of the information
is seemingly only available in the form of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This article targets an advanced audience who are already familiar with
the 3D printing. In this article I will try to collect some information I haven&amp;rsquo;t
found written down in a single place yet. In particular, a lot of the information
is seemingly only available in the form of YouTube videos that take a long time
to get to the&amp;nbsp;point.&lt;/p&gt;
&lt;p&gt;This article assumes some familiarity with &lt;span class="caps"&gt;CAD&lt;/span&gt; and 3D printing. Basic concepts
and terminology is not&amp;nbsp;explained.&lt;/p&gt;
&lt;h1 id="basics-of-vase-mode"&gt;Basics of vase&amp;nbsp;mode&lt;/h1&gt;
&lt;p&gt;With that out of the way what is this about? Vase mode is a printing mode
where the printer prints a spiral path, with no seams. This is fast, avoids
the visual blemishes of the seam but also has some&amp;nbsp;downsides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Only&lt;/em&gt; a single perimeter. This potentially means weaker&amp;nbsp;parts.&lt;/li&gt;
&lt;li&gt;No disconnected areas (per layer), you have to print with a single&amp;nbsp;path.&lt;/li&gt;
&lt;li&gt;No internal geometry. No infill. No top&amp;nbsp;layers.&lt;/li&gt;
&lt;li&gt;No&amp;nbsp;supports.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Typically, it gets used for vases and pots. Thus, the name. Here is a crude
example (I&amp;rsquo;m not an aesthetics focused designer, so imagine something prettier
than this. If it fits and functions, it ships in my&amp;nbsp;book):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Vase mode example" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/traditional_vase.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;Of note here is that the model itself isn&amp;rsquo;t hollow, but the slicer will make it
hollow for you (since it only prints a single perimeter). In PrusaSlicer this
setting is found at &amp;ldquo;Print Settings&amp;rdquo; &amp;rarr; &amp;ldquo;Layers and perimeters&amp;rdquo; &amp;rarr; &amp;ldquo;Vertical shells&amp;rdquo; &amp;rarr; &amp;ldquo;Spiral vase&amp;rdquo;.
OrcaSlicer etc should have the same or similar setting as well somewhere else.
I have no idea about&amp;nbsp;Cura.&lt;/p&gt;
&lt;p&gt;But there are some ways to stretch this mode to the limits, and that is what this
article is about. This will make vase mode useful for more than just simple vases.
And that can often be the fastest and lightest way to print a part, &lt;em&gt;if&lt;/em&gt; you can pull it&amp;nbsp;off.&lt;/p&gt;
&lt;p&gt;To understand the tricks you do need to understand how vase mode works though.
It takes solid geometry, and takes the outline of it. What is inside doesn&amp;rsquo;t matter.
It will be&amp;nbsp;ignored:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Broken vase mode" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/broken_vase.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;As can be seen, while the hole exists in the bottom solid layers, the slicer ignores
it above that&amp;nbsp;point.&lt;/p&gt;
&lt;p&gt;So what can we do above&amp;nbsp;that?&lt;/p&gt;
&lt;h1 id="internal-geometry-via-slits"&gt;Internal geometry via&amp;nbsp;slits&lt;/h1&gt;
&lt;p&gt;The idea comes from the &lt;span class="caps"&gt;RC&lt;/span&gt; plane 3D printing community, where they want to print
lightweight but strong parts. In particular wings with internal supporting geometry.&lt;sup id="fnref:wings"&gt;&lt;a class="footnote-ref" href="#fn:wings"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;There are two main tricks for unconventional vase mode prints. Let&amp;rsquo;s start with slits,
as the next trick builds upon this first trick. As I&amp;rsquo;m no aircraft wing designer
I will use other geometry for illustration purposes. The idea is useful in other
contexts than &lt;span class="caps"&gt;RC&lt;/span&gt; wings, that is the whole point of this&amp;nbsp;article.&lt;/p&gt;
&lt;p&gt;Make a slit into the part. The left is for demonstration only, you need the slit
to be really thin, 0.0001&lt;sup id="fnref:experiment"&gt;&lt;a class="footnote-ref" href="#fn:experiment"&gt;1&lt;/a&gt;&lt;/sup&gt; mm or so, as shown on the&amp;nbsp;right:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Slit example" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/slit1.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;If we extrude this into a block and slice it, PrusaSlicer will see this slit and
print an outer perimeter going into the part, making a sort of internal support.
You are basically modelling the infill yourself&amp;nbsp;now:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Slit example extruded" class="center" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/slit2.avif" width="75%"/&gt;&lt;/p&gt;
&lt;p&gt;If you try this, it will not work for you. This is because you are missing a
crucial setting in PrusaSlicer. By default, PrusaSlicer will merge together close
parts of the model. You need to change
&amp;ldquo;Printer Settings&amp;rdquo; &amp;rarr; &amp;ldquo;Advanced&amp;rdquo; &amp;rarr; &amp;ldquo;Slicing&amp;rdquo; &amp;rarr; &amp;ldquo;Slice gap closing radius&amp;rdquo;.
Set it to 0.0.&lt;sup id="fnref:gap"&gt;&lt;a class="footnote-ref" href="#fn:gap"&gt;3&lt;/a&gt;&lt;/sup&gt; Otherwise, none of this will&amp;nbsp;work.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Slit example extruded" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/slit3.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;For our example with a hole in the middle from the introduction we could get the
following&amp;nbsp;result:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Slit example with hole" class="center" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/fixed_vase.avif" width="75%"/&gt;&lt;/p&gt;
&lt;p&gt;Note that the slit will be visible and you can feel it with your fingers, but it will
be a fairly smooth indentation, not a sharp&amp;nbsp;edge.&lt;/p&gt;
&lt;h1 id="double-walls"&gt;Double&amp;nbsp;walls&lt;/h1&gt;
&lt;p&gt;Now, let&amp;rsquo;s expand on this technique to make it even more useful: Have you ever
wanted to use vase mode but with two perimeters? We can build upon the previous
trick to make a double&amp;nbsp;wall:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Double wall example" class="center" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/double_wall1.avif" width="75%"/&gt;&lt;/p&gt;
&lt;p&gt;This is done by making a slit through into the hollow inside and making sure the
part itself is exactly wide enough for two perimeters that touch. You can find the
width you should use by going into PrusaSlicer (with the same settings that you plan
to use to print with) and looking at the info text in
&amp;ldquo;Print Settings&amp;rdquo; &amp;rarr; &amp;ldquo;Layers and perimeters&amp;rdquo; &amp;rarr; &amp;ldquo;Vertical&amp;nbsp;shells&amp;rdquo;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Value in PrusaSlicer" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/double_wall2.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;That is the value you want to use for this to work&amp;nbsp;correctly.&lt;/p&gt;
&lt;p&gt;We can build upon this to make our internal geometry touch the opposite wall, like&amp;nbsp;so:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Double wall CAD" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/double_wall3.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;We can also use this to anchor a slit to the outside wall. This allows us to anchor
internal geometry to the outside wall without poking through. In fact, to ensure we
have a single continuous outline, all but one slit must be done like this. The following
picture shows what you need to do (note that the double wall thickness is 0.87 mm in this
example, it will change depending on other&amp;nbsp;settings):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Double wall with slit to outside" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/slit_to_edge_double_width.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;These two tricks presented so far form the basis of what I have seen called &amp;ldquo;unconventional vase mode&amp;rdquo;.&lt;sup id="fnref:rahix"&gt;&lt;a class="footnote-ref" href="#fn:rahix"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;But there are some more tricks related to vase mode that are worth knowing&amp;nbsp;about.&lt;/p&gt;
&lt;h1 id="extrusion-width"&gt;Extrusion&amp;nbsp;width&lt;/h1&gt;
&lt;p&gt;To make a vase mode stronger, you can increase the extrusion width. The general
recommendation is that you can go to about 2x the nozzle diameter and keep good
quality. This works, since the nozzle has a bit of a flat spot around the&amp;nbsp;orifice.&lt;/p&gt;
&lt;p&gt;However, British Youtuber &amp;ldquo;Lost in Tech&amp;rdquo; did
&lt;a href="https://www.youtube.com/watch?v=0DAP5Zm1jvk"&gt;some tests&lt;/a&gt; showing that you can go
way further than that, but I haven&amp;rsquo;t tested this myself, and quality does eventually
start going down. It might be worth looking into if this is useful to&amp;nbsp;you.&lt;/p&gt;
&lt;p&gt;In PrusaSlicer you can change this in &amp;ldquo;Print Settings&amp;rdquo; &amp;rarr; &amp;ldquo;Advanced&amp;rdquo; &amp;rarr; &amp;ldquo;Extrusion width&amp;rdquo;.
For vase mode &amp;ldquo;External perimeters&amp;rdquo; is what matters (above the solid base layers, that&amp;nbsp;is):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Extrusion width in PrusaSlicer" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/extrusion_width.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;Remember to rescale any double walls to fit the new extrusion width. It might
be a good idea to use a variable in your &lt;span class="caps"&gt;CAD&lt;/span&gt; model to make it easier to update
(at least if you use parametric &lt;span class="caps"&gt;CAD&lt;/span&gt; like OnShape, FreeCAD or Fusion 360 which
support&amp;nbsp;variables).&lt;/p&gt;
&lt;h1 id="fake-vase-mode"&gt;Fake vase&amp;nbsp;mode&lt;/h1&gt;
&lt;p&gt;Finally, if you absolutely cannot print something in vase mode you can still get
most of the benefits by what I have seen called &amp;ldquo;fake vase mode&amp;rdquo;&lt;sup id="fnref:fake"&gt;&lt;a class="footnote-ref" href="#fn:fake"&gt;5&lt;/a&gt;&lt;/sup&gt;. To
understand this, we should first consider exactly what settings vase mode changes.
In PrusaSlicer vase mode changes the following&amp;nbsp;settings:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Single perimeter (except for the first few bottom&amp;nbsp;layers).&lt;/li&gt;
&lt;li&gt;No top&amp;nbsp;layers.&lt;/li&gt;
&lt;li&gt;No infill (except for the first few bottom&amp;nbsp;layers).&lt;/li&gt;
&lt;li&gt;No&amp;nbsp;supports&lt;/li&gt;
&lt;li&gt;Disables the setting &amp;ldquo;ensure vertical shell&amp;nbsp;thickness&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Prints in a continuous spiral&amp;nbsp;path.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can do all of those except 6 by hand in the slicer. And you can mix and
match those first five things as you see&amp;nbsp;fit.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s investigate this via a case study rather than simplified theoretical
examples like we have done so&amp;nbsp;far&lt;/p&gt;
&lt;h2 id="case-study-spheres-on-sticks"&gt;Case study: spheres on&amp;nbsp;sticks&lt;/h2&gt;
&lt;p&gt;I needed some spheres on the end of wooden sticks, to hold up a bird net over
my strawberries on my balcony. I didn&amp;rsquo;t want the net to lie on the plants directly,
and I needed something on the end of the sticks so that the net wouldn&amp;rsquo;t tear.
Thus, spheres (or rather: truncated spheres for print bed adhesion and overhang
reasons) on&amp;nbsp;sticks.&lt;/p&gt;
&lt;p&gt;Here is the basic design in a section&amp;nbsp;view:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sphere on sticks section view" class="center" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/sphere_on_stick1.avif" width="50%"/&gt;&lt;/p&gt;
&lt;p&gt;This doesn&amp;rsquo;t quite work in vase mode, because the top of the sphere has very
shallow overhangs. And the top needs to be smooth. (The &amp;ldquo;roof&amp;rdquo; of the internal
hole is fine, thanks to the cone shape.) It is so close, we can &lt;em&gt;almost&lt;/em&gt; use vase&amp;nbsp;mode.&lt;/p&gt;
&lt;p&gt;So first I designed this in &lt;span class="caps"&gt;CAD&lt;/span&gt;. We have a slit from the outside to the centre,
as well as some slits from the centre that goes &lt;em&gt;almost&lt;/em&gt; to the outside. In fact,
they go to the &amp;ldquo;recommended object thin wall thickness&amp;rdquo; mentioned before.
(Note that the slits do &lt;em&gt;not&lt;/em&gt; go down into the solid bottom layers, for some additional&amp;nbsp;strength.)&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sphere on stick CAD with slits" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/sphere_on_stick2.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;This results in the following in&amp;nbsp;PrusaSlicer:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sphere on stick in PrusaSlicer" class="center" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/sphere_on_stick3.avif" width="75%"/&gt;&lt;/p&gt;
&lt;p&gt;Like true vase mode, I used zero infill. But I enabled &amp;ldquo;Ensure vertical shell thickness&amp;rdquo;
and 1 top solid layer. This added a tiny bit of material just below the shallow top
of the dome, making it printable, but still lighter than printing normally. Then
I used a layer range modifier to &lt;em&gt;disable&lt;/em&gt; &amp;ldquo;ensure vertical shell thickness&amp;rdquo; for
the lower part of the print where it wasn&amp;rsquo;t needed, as PrusaSlicer wanted to add
some material on the inside of the lower layers as&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sphere on stick in PrusaSlicer with additional extrusions" class="center" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/sphere_on_stick4.avif" width="75%"/&gt;&lt;/p&gt;
&lt;p&gt;I also increased the extrusion width to 0.8 mm (with a 0.4 mm nozzle) to get
additional strength, and I used scarf seams to make the outside seam almost&amp;nbsp;invisible.&lt;/p&gt;
&lt;p&gt;You can go further from true vase mode though: You could have an inner and outer
perimeter like traditional non-vase slicing, but still model your own infill only
where needed. You will get seams obviously, but you might still be able to print
faster and save weight. We are moving further from true vase mode here, but only
you can decide what exactly is best for your&amp;nbsp;print:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sphere on stick with no slit from outside to inside" class="center" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/sphere_on_stick5.avif" width="75%"/&gt;&lt;/p&gt;
&lt;p&gt;In fact, when I printed some of these spheres, the version without a slit to the
outside ended up the best looking&lt;sup id="fnref:material"&gt;&lt;a class="footnote-ref" href="#fn:material"&gt;6&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sphere on stick printed" class="center" loading="lazy" src="https://vorpal.se/images/unconventional_vase_mode/sphere_on_stick_printed.avif" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;The slit is visible, but on the part printed without a slit extending to the outside
there are no visible seams at all. The unevenness at the top is due to me filing away
a small blob that the nozzle left behind as it pulled away at the end. It is smooth
to the touch but reflects the light&amp;nbsp;differently.&lt;/p&gt;
&lt;h1 id="conclusions_1"&gt;Conclusions&lt;/h1&gt;
&lt;p&gt;Vase mode and &amp;ldquo;fake vase mode&amp;rdquo; is an often underused printing mode for
functional parts, and it can be used to save weight and print time. The difference
will be most noticeable on larger parts, on smaller parts 10 vs 15 minutes might
not be worth the extra design effort (unless you are planning to print many
copies of the same&amp;nbsp;part).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a bit disappointed that the slit was as visible from the outside as it was.
From the videos about &lt;span class="caps"&gt;RC&lt;/span&gt; aircraft wings that I saw I expected this to be less
noticeable. But &amp;ldquo;fake vase mode&amp;rdquo; still comes to the rescue here, offering most
of the benefits. And when combined with scarf joint seams (which I found truly
impressive, first time I tried it), I don&amp;rsquo;t really see the need for true vase
mode any more. You might as well get the best of both&amp;nbsp;worlds.&lt;/p&gt;
&lt;p&gt;I did not find any written resource online summarizing these techniques, so I hope
this post is useful not just to remind myself in the future, but also to others
looking for this information. With that in mind, below is a cheat sheet of the
important points and settings to&amp;nbsp;remember.&lt;/p&gt;
&lt;p&gt;These techniques require tuning settings in your slicer. This may not be possible
if you are printing with at a commercial print farm, or targeting people slicing
with a dumbed down web based slicer (as has recently been launched by both
Printables and Makerworld). But it would be a shame if such dumbed down slicers
restricted what we could design and publish. I will always try to make the most
what both &lt;span class="caps"&gt;CAD&lt;/span&gt; and the slicer exposes to me.&lt;sup id="fnref:fullcontrol"&gt;&lt;a class="footnote-ref" href="#fn:fullcontrol"&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Do you have some other tips or tricks for vase mode? Did I get something wrong?
Comment on &lt;a href="https://old.reddit.com/r/prusa3d/comments/1lik51r/tutorial_on_advanced_vase_mode_printing"&gt;Reddit&lt;/a&gt;
or on &lt;a href="https://programming.dev/post/32745940"&gt;Lemmy&lt;/a&gt; and I will likely see it&amp;nbsp;(eventually).&lt;/p&gt;
&lt;h2 id="cheat-sheet"&gt;Cheat&amp;nbsp;sheet&lt;/h2&gt;
&lt;p&gt;Want to quickly remind yourself of the core ideas of this article when you are designing
your next part? Here is a quick cheat&amp;nbsp;sheet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Slits: Use slits to add internal geometry.&lt;ul&gt;
&lt;li&gt;0.0001 mm wide (or 0.001 if your &lt;span class="caps"&gt;CAD&lt;/span&gt; software doesn&amp;rsquo;t like you that&amp;nbsp;day).&lt;/li&gt;
&lt;li&gt;PrusaSlicer: Set &amp;ldquo;Print Settings&amp;rdquo; &amp;rarr; &amp;ldquo;Advanced&amp;rdquo; &amp;rarr; &amp;ldquo;Slicing&amp;rdquo; &amp;rarr; &amp;ldquo;Slice gap closing radius&amp;rdquo; to&amp;nbsp;0.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Double walls: Use double walls for more strength and to connect slits to the opposite wall.&lt;ul&gt;
&lt;li&gt;PrusaSlicer: &amp;ldquo;Print Settings&amp;rdquo; &amp;rarr; &amp;ldquo;Layers and perimeters&amp;rdquo; &amp;rarr; &amp;ldquo;Vertical shells&amp;rdquo;
  (Look at info text to find width you need to use for your current print&amp;nbsp;settings.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Extrusion width: You can increase the extrusion width to 2x the nozzle diameter
  for additional strength with no quality downsides. You might be able to go even
  further, but eventually quality will start going down.&lt;ul&gt;
&lt;li&gt;PrusaSlicer: &amp;ldquo;Print Settings&amp;rdquo; &amp;rarr; &amp;ldquo;Advanced&amp;rdquo; &amp;rarr; &amp;ldquo;Extrusion width&amp;rdquo; &amp;rarr; &amp;ldquo;External&amp;nbsp;perimeters&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Fake vase mode: You don&amp;rsquo;t need to use vase mode to get most of the benefits.
  You can mix and match all parts of normal vase mode except for the continuous
  spiral path. But consider scarf joints to hide&amp;nbsp;seams.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:experiment"&gt;
&lt;p&gt;You might need to experiment with the specific value to make your &lt;span class="caps"&gt;CAD&lt;/span&gt;
program and slicer happy. With OnShape, sometimes 0.0001 mm works, sometimes only
0.001 mm works (or Onshape doesn&amp;rsquo;t see the slit), and I don&amp;rsquo;t know why exactly.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:experiment" title="Jump back to footnote 1 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:wings"&gt;
&lt;p&gt;The first mention I can find of this is in &lt;a href="https://www.youtube.com/watch?v=QJjhMan6T_E"&gt;this video&lt;/a&gt; by Tom Stanton, but I cannot say for sure that this is where it originated.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:wings" title="Jump back to footnote 2 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:gap"&gt;
&lt;p&gt;I found this solution from &lt;a href="https://forum.prusa3d.com/forum/prusaslicer/problem-with-slicing-complex-vase-mode-models/"&gt;this forum post&lt;/a&gt;.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:gap" title="Jump back to footnote 3 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:rahix"&gt;
&lt;p&gt;Unconventional vase mode is briefly mentioned in
&lt;a href="https://blog.rahix.de/design-for-3d-printing/"&gt;this excellent but long blog post&lt;/a&gt;
about designing for 3D printing. I strongly recommend reading it if you want to make
your &lt;span class="caps"&gt;CAD&lt;/span&gt; designs portable between different printers and for general tips on how to
design to avoid supports, and in general make full use of the peculiarities of the
manufacturing method.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:rahix" title="Jump back to footnote 4 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fake"&gt;
&lt;p&gt;I &amp;ldquo;semi-invented&amp;rdquo; this method myself, but then found out I wasn&amp;rsquo;t first.
I was thinking &amp;ldquo;wouldn&amp;rsquo;t it be possible to&amp;hellip;&amp;rdquo; and then I googled and found
&lt;a href="https://www.youtube.com/watch?v=t0NrCey_u7o"&gt;this video&lt;/a&gt; by &amp;ldquo;&lt;span class="caps"&gt;BV3D&lt;/span&gt;: Bryan Vines&amp;rdquo;,
that already discussed this idea, though it takes a while to get to the point.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:fake" title="Jump back to footnote 5 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:material"&gt;
&lt;p&gt;Printed with AddNorth Economy &lt;span class="caps"&gt;PETG&lt;/span&gt;, white.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:material" title="Jump back to footnote 6 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fullcontrol"&gt;
&lt;p&gt;I even considered using &lt;a href="https://fullcontrol.xyz/"&gt;Full Control &lt;span class="caps"&gt;XYZ&lt;/span&gt;&lt;/a&gt;
for a minute to have true vase mode and then switch back to non-vase mode on top.
In the end I came to my senses and decided not to write my model with Python code.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:fullcontrol" title="Jump back to footnote 7 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="3D Printing"></category><category term="3D Printing"></category></entry><entry><title>Filkoll - The fastest command-not-found handler</title><link href="https://vorpal.se/posts/2025/mar/25/filkoll-the-fastest-command-not-found-handler/" rel="alternate"></link><published>2025-03-25T00:00:00+01:00</published><updated>2025-03-25T00:00:00+01:00</updated><author><name>Arvid Norlander</name></author><id>tag:vorpal.se,2025-03-25:/posts/2025/mar/25/filkoll-the-fastest-command-not-found-handler/</id><summary type="html">&lt;p&gt;I recently got annoyed at the command-not-found handler found in Arch Linux.
So I wrote &lt;a href="https://github.com/VorpalBlade/filkoll"&gt;my own faster&lt;/a&gt; implementation. But first lets back up
for a second, what am I even talking&amp;nbsp;about?&lt;/p&gt;
&lt;h1 id="the-problem"&gt;The&amp;nbsp;problem&lt;/h1&gt;
&lt;p&gt;A command-not-found handler is a program that runs when you type a command in your …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently got annoyed at the command-not-found handler found in Arch Linux.
So I wrote &lt;a href="https://github.com/VorpalBlade/filkoll"&gt;my own faster&lt;/a&gt; implementation. But first lets back up
for a second, what am I even talking&amp;nbsp;about?&lt;/p&gt;
&lt;h1 id="the-problem"&gt;The&amp;nbsp;problem&lt;/h1&gt;
&lt;p&gt;A command-not-found handler is a program that runs when you type a command in your terminal
that is not found in&amp;nbsp;your &lt;code&gt;PATH&lt;/code&gt;. It will print some suggestions such&amp;nbsp;as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="go"&gt;arch ❯ uv&lt;/span&gt;
&lt;span class="go"&gt;uv may be found in the following packages:&lt;/span&gt;
&lt;span class="go"&gt;  extra/uv 0.6.2-1        /usr/bin/uv&lt;/span&gt;

&lt;span class="go"&gt;debian ❯ postfix                               &lt;/span&gt;
&lt;span class="go"&gt;Command 'postfix' not found, but can be installed with:&lt;/span&gt;
&lt;span class="go"&gt;sudo apt install postfix&lt;/span&gt;

&lt;span class="go"&gt;debian ❯ postfffix&lt;/span&gt;
&lt;span class="go"&gt;Command 'postfffix' not found, did you mean:&lt;/span&gt;
&lt;span class="go"&gt;  command 'postfix' from deb postfix&lt;/span&gt;
&lt;span class="go"&gt;Try: sudo apt install &amp;lt;deb name&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As can be seen, it does output a few different ways on different Linux distributions.
I found the one in Arch Linux (provided by &lt;a href="https://github.com/falconindy/pkgfile"&gt;pkgfile&lt;/a&gt;) slow (taking ~1.6 s). It also
doesn&amp;rsquo;t implement fuzzy matching on typos. To be fair, pkgfile does a lot more than
just command-not-found handling, but for this use case that isn&amp;rsquo;t&amp;nbsp;relevant.&lt;/p&gt;
&lt;p&gt;I did some profiling and found that pkgfile was spending most of its time parsing
a cache file for the &amp;ldquo;extra&amp;rdquo; repository. A very large file (478 MiB). I reported a
&lt;a href="https://github.com/falconindy/pkgfile/issues/71"&gt;bug&lt;/a&gt; about this, but while waiting
for it to get fixed I decided &amp;ldquo;I can do better&amp;rdquo; and &amp;ldquo;this sounds like a fun weekend&amp;nbsp;project&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I should mention that pkgfile has improved immensely as a result of that bug report.
It now only takes on average 128.3 ms to run. But by that point I had already finished
my program. And my program only takes ~5 ms to do the same query. So now I&amp;rsquo;m 25.6x
faster, instead of 320x faster. And I also use 102x less &lt;span class="caps"&gt;CPU&lt;/span&gt; time to do so. Oh, and I do
fuzzy searching (which pkgfile doesn&amp;rsquo;t&amp;nbsp;do).&lt;/p&gt;
&lt;p&gt;If you want to go use my program look at the &lt;a href="https://github.com/VorpalBlade/filkoll"&gt;github page&lt;/a&gt;. The rest
of this blog post will be aimed at developers who are interested in the technical
details of how I did this. I am using Rust, but the concepts should be applicable
to other languages as well (especially C, C++, Zig and other low level&amp;nbsp;languages).&lt;/p&gt;
&lt;h1 id="design-phase"&gt;Design&amp;nbsp;phase&lt;/h1&gt;
&lt;p&gt;The first step of any project like this is to figure out the design. And the
first step of that is figuring out what you want to do. Pkgfile does a lot more
than just command-not-found handling: It also does forward and reverse searching
for which package provides a particular file.&amp;nbsp;Unlike &lt;code&gt;pacman -Qo&lt;/code&gt; it will provide
this info even when the package isn&amp;rsquo;t installed. While handy, this is a general
functionality I don&amp;rsquo;t need to solve if I want to write &lt;strong&gt;the fastest&lt;/strong&gt; possible
command-not-found handler. I&amp;rsquo;m more than happy to use pkgfile for those other
use&amp;nbsp;cases.&lt;/p&gt;
&lt;p&gt;So I decided to hyper-specialise on command-not-found handling. Okay, that defines
the scope. Next we need to know where to get the data to search. Pkgfile downloads
the data from the pacman mirrors itself, but it turns out pacman can do this for
us, using&amp;nbsp;the &lt;code&gt;pacman -F&lt;/code&gt; sub-command. In order to simplify I will piggy back on
that. No need to check that I&amp;rsquo;m not redownloading changed files, pacman can do
that for me. When I want to update my cache files I just first&amp;nbsp;run &lt;code&gt;pacman -Fy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Wait a second? If pacman has this downloaded already, why do I need to make cache files?
Well, as it turns out, those files are big compressed
&lt;a href="https://en.wikipedia.org/wiki/Tar_(computing)"&gt;tar&lt;/a&gt; archives, and they are mapping
from package, to the files that package provides. I need the opposite mapping. So I
need to build some sort of cache file with the subset of data I&amp;rsquo;m interested in,
organized such that it is quick to&amp;nbsp;search.&lt;/p&gt;
&lt;p&gt;At this point the general approach is clear, a command with two&amp;nbsp;sub-commands:
&lt;code&gt;update&lt;/code&gt; (updates the cache)&amp;nbsp;and &lt;code&gt;binary&lt;/code&gt; (searches the cache for a&amp;nbsp;binary).&lt;/p&gt;
&lt;p&gt;We should also identify our &amp;ldquo;hot path&amp;rdquo;. That is, which of these sub-commands matter
the most that it is quick? The answer&amp;nbsp;is &lt;code&gt;binary&lt;/code&gt;, this will be run&amp;nbsp;interactively.
&lt;code&gt;update&lt;/code&gt; will be run once per day on a background timer. So we should optimise&amp;nbsp;for
&lt;code&gt;binary&lt;/code&gt; at the expense&amp;nbsp;of &lt;code&gt;update&lt;/code&gt; (though we&amp;nbsp;want &lt;code&gt;update&lt;/code&gt; to be as fast as
possible within those constraints of course). Identifying this &amp;ldquo;hot path&amp;rdquo; early
is something I have found very useful across many interactive command line tools
I have&amp;nbsp;written.&lt;/p&gt;
&lt;h1 id="implementation"&gt;Implementation&lt;/h1&gt;
&lt;p&gt;The first step of the implementation is of course another design step: What data
structures and libraries should we use. I wanted to try out &lt;a href="https://rkyv.org/"&gt;rkyv&lt;/a&gt; for a while and
this project should be a good fit: It is a library for zero-copy deserialisation.
That is: once I load the cache file I don&amp;rsquo;t need to do any decoding of the data
in the file. I can use it in memory as&amp;nbsp;is.&lt;/p&gt;
&lt;p&gt;One thing you have to think about when doing zero-copy deserialisation is that you
can&amp;rsquo;t use compression. At least not traditional compression. If you have compressed
files you need to decompress them first, so no zero-copy for you. As such we need to
look for alternative approaches to shrink the data size. I will be using string
interning (a technique I&amp;rsquo;m familar with since earlier projects). More on that&amp;nbsp;later.&lt;/p&gt;
&lt;p&gt;Then there was the question of fuzzy search that I wanted to do. That I had to
do some research on, and I ended up with a &amp;ldquo;simpler is better&amp;rdquo; solution. More on
that later as&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;I will also use some of the usual suspects for a Rust project: &lt;a href="https://lib.rs/crates/clap"&gt;clap&lt;/a&gt; for argument parsing,
&lt;a href="https://docs.rs/rayon/"&gt;rayon&lt;/a&gt; for a thread pool,&amp;nbsp;etc.&lt;/p&gt;
&lt;p&gt;So lets look at the sub-problems we have ahead of&amp;nbsp;us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Zero-copy&amp;nbsp;deserialisation&lt;/li&gt;
&lt;li&gt;String&amp;nbsp;interning&lt;/li&gt;
&lt;li&gt;Efficient data&amp;nbsp;structures&lt;/li&gt;
&lt;li&gt;Building the&amp;nbsp;cache&lt;/li&gt;
&lt;li&gt;Searching the&amp;nbsp;cache&lt;/li&gt;
&lt;li&gt;Fuzzy&amp;nbsp;searching&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these will be covered in a section&amp;nbsp;below.&lt;/p&gt;
&lt;h2 id="zero-copy-deserialisation"&gt;Zero-copy&amp;nbsp;deserialisation&lt;/h2&gt;
&lt;p&gt;You might naively think that you could just write&amp;nbsp;a &lt;code&gt;struct&lt;/code&gt; to a file and load
it back into memory. This &lt;em&gt;can&lt;/em&gt; work in specific circumstances, but if you have
anything with an indirection (pointer), that breaks down. The struct will probably
not be loaded at the same location as where you created it before saving it. So
this is why I turned to &lt;a href="https://rkyv.org/"&gt;rkyv&lt;/a&gt; as it solves this problem for us. It will
use offsets in the data instead of absolute pointers. Like any serialization library
in Rust it quite ergonomic to use thanks to Rust&amp;rsquo;s derive macro&amp;nbsp;system:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MyStruct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rust &lt;code&gt;derive&lt;/code&gt; takes care of the hard work for you. For the benefit of those who
don&amp;rsquo;t know Rust:&amp;nbsp;what &lt;code&gt;derive&lt;/code&gt; does is passing the annotated code to a pre-processor
(a &amp;ldquo;proc macro&amp;rdquo; in Rust parlance) that can generate code based on the input it gets.
The expanded code takes care of all the tricky details for you, in this case for
serializing and deserializing the struct. Many other derives exist, both built
into the standard library and as separate&amp;nbsp;libraries.&lt;/p&gt;
&lt;p&gt;Rkyv is not compatible with the&amp;nbsp;popular &lt;code&gt;serde&lt;/code&gt; library for Rust (another derive
based&amp;nbsp;library): &lt;code&gt;serde&lt;/code&gt; fundamentally can&amp;rsquo;t do zero-copy except for a small subset
of the data in very specific&amp;nbsp;circumstances.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not going to deep dive into how rkyv works, but some information is available
in the official &lt;a href="https://rkyv.org/"&gt;rkyv mdbook&lt;/a&gt; for those that are&amp;nbsp;interested.&lt;/p&gt;
&lt;h2 id="string-interning"&gt;String&amp;nbsp;interning&lt;/h2&gt;
&lt;p&gt;There are many strings that repeat in our data. Most binaries live&amp;nbsp;in &lt;code&gt;/usr/bin&lt;/code&gt;
or a small handful of other directories. Many packages provide more than one binary
(so package name also repeats many times). We can exploit this duplication to save
on memory and file&amp;nbsp;size.&lt;/p&gt;
&lt;p&gt;String interning is the idea that you store one copy of a string and point to it
many times instead of storing the same string many times. There are many libraries
for this in Rust. None of them support Rkyv from what I could find. So I wrote a
very simple single threaded interner intended to work well together with&amp;nbsp;Rkyv.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Side note: String intering sounds great, why not use it all the time? Well, it
has a cost: You need to look up the string in the interner to get at the data,
the strings are read only, and if the string doesn&amp;rsquo;t live for long that can be
wasted memory unless you implement garbage collection or reference counting
(which also has a cost). So like almost everything in programming, it is&amp;nbsp;situational.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most string interners are intended for use in a single session of the program.
As such they try to optimise for not reallocating memory too much etc, and internally
have complex allocation strategies. I don&amp;rsquo;t need that. In fact, I don&amp;rsquo;t &lt;em&gt;want&lt;/em&gt; that.
My &amp;ldquo;hot path&amp;rdquo; is searching. That is what I should optimise for. What is the cheapest
possible string interner for my use case (even if building it is more&amp;nbsp;expensive)?&lt;/p&gt;
&lt;p&gt;Well, I don&amp;rsquo;t know if it is &lt;strong&gt;the&lt;/strong&gt; cheapest possible, but it is very cheap: A&amp;nbsp;single &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt;.
An interned string is just&amp;nbsp;a &lt;code&gt;u32&lt;/code&gt; offset into&amp;nbsp;that &lt;code&gt;Vec&lt;/code&gt;. It points to&amp;nbsp;a &lt;code&gt;u16&lt;/code&gt; that
is the length of the data (we don&amp;rsquo;t need massively long strings), followed by the data
itself. It is very fast to do a lookup in. And very fast to extract a&amp;nbsp;Rust &lt;code&gt;&amp;amp;str&lt;/code&gt; from.
It is a bit of a pain to build&amp;nbsp;though.&lt;/p&gt;
&lt;p&gt;For a start, how do we know when we have seen a string before? We could just do a
linear search. But that is slow&amp;nbsp;(complexity &lt;span class="math"&gt;\(O\left(n\right)\)&lt;/span&gt;). And even though updating
isn&amp;rsquo;t our fast path, I would like it to have reasonable complexity&amp;nbsp;( &lt;span class="math"&gt;\(O\left(1\right)\)&lt;/span&gt;).
Instead, we need a hash map on the side, but only while building. So we end up&amp;nbsp;with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sd"&gt;/// Use a newtype handle to avoid mixing up incompatible numbers.&lt;/span&gt;
&lt;span class="cp"&gt;#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Handle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="sd"&gt;/// The interner as it exists after creation, ready to be serialized.&lt;/span&gt;
&lt;span class="cp"&gt;#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;StringInterner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="sd"&gt;/// The builder for a string interner&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;StringInternerBuilder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;StringInternerBuilder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Self&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/*...*/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Intern a new string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// (possibly getting back a handle to a previously interned string,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;///  or a new handle)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;intern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Handle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/*...*/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Finalize the builder and create the readonly version&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;///&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// This consumes this builder.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;into_readonly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;StringInterner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/*...*/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ArchivedStringInterner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ArchivedHandle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/*...*/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Wait, what is going on&amp;nbsp;with &lt;code&gt;ArchivedStringInterner&lt;/code&gt; and &lt;code&gt;ArchivedHandle&lt;/code&gt;? Well,
that is how Rkyv works. Since it is zero-copy it cannot &lt;em&gt;in general&lt;/em&gt; use the same
data type when serializing and deserializing. So you get back an &amp;ldquo;archived&amp;rdquo;
version of it, that uses relative offsets, rather than&amp;nbsp;pointers.&lt;/p&gt;
&lt;p&gt;Another point here is that of &amp;ldquo;newtypes&amp;rdquo;. This is fairly common pattern in Rust.
For those who don&amp;rsquo;t know Rust, it is just a wrapper around an inner type such
that you will get a compiler error if you mix variables of different newtypes.
Good to reduce the risk of bugs from accidentially mixing&amp;nbsp;e.g. &lt;code&gt;UserId(u64)&lt;/code&gt; with
&lt;code&gt;PostId(u64)&lt;/code&gt;.&amp;nbsp;The &lt;code&gt;Handle&lt;/code&gt; is exactly that: a newtype&amp;nbsp;around &lt;code&gt;u32&lt;/code&gt; (unsigned 32-bit&amp;nbsp;integer).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at the implementation&amp;nbsp;of &lt;code&gt;get&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sd"&gt;/// Get the string for a given handle&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ArchivedHandle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_utf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid utf8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="sd"&gt;/// Get the raw bytes for a given handle&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;ArchivedHandle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Extract the offset into our Vec from the handle&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_native&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Check bounds (though rust will panic later on the built in bounds check if&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// we are out of bounds, so this isn't strictly needed, just provides a more&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// obvious error).&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="fm"&gt;debug_assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Get the string size at offset (u16)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_le_bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Get the string at offset + 2 (based on the size we just read)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nice, entirely safe Rust. And, as we are not going to be looking up many strings,
it isn&amp;rsquo;t worth hyper optimising this to the point of&amp;nbsp;using &lt;code&gt;unsafe&lt;/code&gt;. In particular
it wouldn&amp;rsquo;t be safe to &lt;em&gt;not&lt;/em&gt; do &lt;span class="caps"&gt;UTF&lt;/span&gt;-8 validation: It is possible to mix up
handles from different interners. So in general we can&amp;rsquo;t trust the handle to point
to anything&amp;nbsp;sensible.&lt;/p&gt;
&lt;p&gt;What are the pros and cons of this interner? As I said above, it is very fast
once built. But it is single threaded to build. And since it is a&amp;nbsp;single &lt;code&gt;Vec&lt;/code&gt;:
if the vector needs to grow, the whole vector needs to be reallocated, which might
result in an expensive memory copy if it can&amp;rsquo;t grow in place. That will affect our
cache build speed. But that is &lt;em&gt;not&lt;/em&gt; on the hot&amp;nbsp;path.&lt;/p&gt;
&lt;h2 id="efficient-data-structures"&gt;Efficient data&amp;nbsp;structures&lt;/h2&gt;
&lt;p&gt;Okay, we now have a string interner. What else do we need? Well, we need a root
object to serialize. That needs to have a lookup table for binary names to packages.
After a bit of back and forth I ended up on the following (excluding rkyv derives&amp;nbsp;etc):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;DataRoot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Name of repository (e.g. core or extra)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Interner for paths and package names&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;interner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;StringInterner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Mapping from binary name to data about binary&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;binaries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SmallVec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Record&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Which package provides the binary&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;PackageRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// Which directory is the binary in (e.g. /usr/bin)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;DirectoryRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;PackageRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;DirectoryRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Okay, what is going on here? Well, there are more newtypes around&amp;nbsp;the &lt;code&gt;Handle&lt;/code&gt;
from earlier. Again to prevent mixing things&amp;nbsp;up.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SmallVec&lt;/code&gt; is from a crate (Rust library) of the same name. It will store up
to N elements inline. It will only allocate separately on the heap if it outgrows
that. Since most binaries are only provided by a single package, this is a good fit.
We expect the length to be 1. As it turns out length 2 is free:&amp;nbsp;A &lt;code&gt;SmallVec&lt;/code&gt;
on a 64-bit build is always a minimum 24 bytes anyway, so we get two elements for
free in this&amp;nbsp;case.&lt;/p&gt;
&lt;p&gt;So now we have a mapping from binary names to info about the&amp;nbsp;binary.&lt;/p&gt;
&lt;h2 id="building-the-cache"&gt;Building the&amp;nbsp;cache&lt;/h2&gt;
&lt;p&gt;When building we need to first download the data. As mentioned in the
&lt;a href="#design-phase"&gt;design section&lt;/a&gt;: we piggy back&amp;nbsp;on &lt;code&gt;pacman -Fy&lt;/code&gt; for that. That
creates several files&amp;nbsp;in &lt;code&gt;/var/lib/pacman/sync&lt;/code&gt;. We need to process&amp;nbsp;the
&lt;code&gt;*.files&lt;/code&gt; files. These are compressed tar archives. I decided to
process one such file per thread. This naturally creates a cache file per input&amp;nbsp;archive.&lt;/p&gt;
&lt;p&gt;We &lt;em&gt;only&lt;/em&gt; need to index binaries&amp;nbsp;in &lt;code&gt;$PATH&lt;/code&gt; so we can filter on that &lt;em&gt;while building&lt;/em&gt;.
This reduces the size of the data immensely. Instead of a few hundred MiB we end
up with about 950 KiB of data (after the interning that&amp;nbsp;is).&lt;/p&gt;
&lt;p&gt;I did a few performance tricks here, such as delaying validating that the paths are
valid &lt;span class="caps"&gt;UTF&lt;/span&gt;-8 until after the initial filtering on paths, and reusing allocations in hot loops.
In the end the bottleneck is processing&amp;nbsp;the &lt;code&gt;extra.files&lt;/code&gt; archive (45 MiB compressed,
511 MiB uncompressed). But I manage in about 1-1.4 seconds on my Skylake era laptop.
Not bad, and since this will run in the background that is fine(&lt;span class="caps"&gt;TM&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;One performance trick I found quite generally useful in many Rust projects is to
use&amp;nbsp;the &lt;code&gt;regex&lt;/code&gt; crate. From other languages (Python, C++, etc) I&amp;rsquo;m used to regexes
being slow, and thus not ideal for high performance code. However, the Rust implementation
is exceptional, and often compiles&lt;sup id="fnref:compiles"&gt;&lt;a class="footnote-ref" href="#fn:compiles"&gt;1&lt;/a&gt;&lt;/sup&gt; down to very performant state machines, exploiting
the specifics of your pattern. I used it to filter&amp;nbsp;on &lt;code&gt;$PATH&lt;/code&gt;. I build a regular
expression that is basically all the elements&amp;nbsp;from &lt;code&gt;$PATH&lt;/code&gt; joined&amp;nbsp;with &lt;code&gt;|&lt;/code&gt; (the
regex &amp;ldquo;or&amp;rdquo; operator). Based on what I have read&amp;nbsp;about &lt;code&gt;regex&lt;/code&gt; I suspect this compiles
down to an &lt;a href="https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm"&gt;aho-corasick&lt;/a&gt;
automaton under the hood (though I don&amp;rsquo;t know how to get such debug info such as
the underlying regex engine out of&amp;nbsp;it).&lt;/p&gt;
&lt;h2 id="searching-the-cache"&gt;Searching the&amp;nbsp;cache&lt;/h2&gt;
&lt;p&gt;I wanted to make the search multi-threaded (obviously). But starting a thread
takes longer than the entire runtime of the single threaded search! So I&amp;rsquo;ll take&amp;nbsp;that.&lt;/p&gt;
&lt;p&gt;How did I get there though? This is where we get into&amp;nbsp;the &lt;code&gt;unsafe&lt;/code&gt; code. (Insert spooky music&amp;nbsp;here.)&lt;/p&gt;
&lt;h3 id="memory-mapping"&gt;Memory&amp;nbsp;mapping&lt;/h3&gt;
&lt;p&gt;For a start, I memory-map the cache files. This uses&amp;nbsp;the &lt;code&gt;memmap2&lt;/code&gt; rust crate.
Doing that memory map&amp;nbsp;is &lt;code&gt;unsafe&lt;/code&gt;. Scary! Well, not really. You just need to
ensure the data doesn&amp;rsquo;t change out under you while you are using&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I think it is time for an aside on unsafe in Rust. And how the community deals with it.
I have seen the entire spectrum from &amp;ldquo;&lt;em&gt;&lt;code&gt;unsafe&lt;/code&gt; outside of the standard library is evil&lt;/em&gt;&amp;rdquo;
to &amp;ldquo;&lt;em&gt;eh, no big deal&lt;/em&gt;&amp;ldquo;. I think it is best to take a middle way on this. You shouldn&amp;rsquo;t be
scared&amp;nbsp;of &lt;code&gt;unsafe&lt;/code&gt; but also don&amp;rsquo;t use it without having a reason. And when you do use it,
read the Safety notes for whatever function you are calling. If you are working with
raw pointers, and especially on the border between raw pointers and references it
gets much more complex (and this page is not the right guide for you). But most of
the time it is just a matter of reading and being&amp;nbsp;careful.&lt;/p&gt;
&lt;p&gt;So lets look at the safety documentation here&amp;nbsp;for &lt;code&gt;memmap2&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All file-backed memory map constructors are&amp;nbsp;marked &lt;code&gt;unsafe&lt;/code&gt; because of the potential for
&lt;em&gt;Undefined Behavior&lt;/em&gt; (&lt;span class="caps"&gt;UB&lt;/span&gt;) using the map if the underlying file is subsequently modified, in or
out of process. Applications must consider the risk and take appropriate precautions when using
file-backed maps. Solutions such as file permissions, locks or process-private (e.g. unlinked)
files exist but are platform specific and&amp;nbsp;limited.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Okay, what does that mean for &lt;em&gt;us&lt;/em&gt;? Well, I needed to go back to the updating code
and ensure it didn&amp;rsquo;t modify the cache files in place. Instead, I create new files
and move them in place once done. On Linux and Unix at least, this is an atomic
replacement. A concurrent reader gets &lt;em&gt;either&lt;/em&gt; the old or new file. Not a mix of&amp;nbsp;both.&lt;/p&gt;
&lt;p&gt;So we can write our own safety&amp;nbsp;comment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// SAFETY:&lt;/span&gt;
&lt;span class="c1"&gt;// * When the file is written it is created anew, not overwritten in place. As&lt;/span&gt;
&lt;span class="c1"&gt;//   such it cannot change under us.&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unsafe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Mmap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I also documented this in the update&amp;nbsp;code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Figure out the file names for the files we write, note that we will write to a temporary file.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cache_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cache_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"binaries"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cache_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;with_extension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"binaries_new"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ... Serialising and writing here ...&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// INVARIANT: Rename to atomically update the file&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// This is a safety requirement to allow mmaping the file during lookup.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cache_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This helps ensure that a future change to the writer doesn&amp;rsquo;t break this assumption by&amp;nbsp;mistake.&lt;/p&gt;
&lt;p&gt;(Side note: I&amp;rsquo;m not considering the case of an attacker modifying the file here, the file
will be owned by root: the update job runs from a systemd timer as root. In my
threat model I trust&amp;nbsp;root.)&lt;/p&gt;
&lt;p&gt;This handling is quite different from what you see with code in most other languages,
including code I myself have written in e.g. C++ or Python before. Everything is
basically an unclear soup of safe and unsafe in those languages (to varying degrees,
there are &lt;em&gt;much&lt;/em&gt; more unsafe things in C++ than in Python, but there are a few
things you can mess up in Python too, though usually you don&amp;rsquo;t get a segfault,
but get weird behaviour or an unexpected exception, etc). Rust forces you think
about these things, which I think is&amp;nbsp;good.&lt;/p&gt;
&lt;h3 id="rkyv-safety-invariants"&gt;Rkyv safety&amp;nbsp;invariants&lt;/h3&gt;
&lt;p&gt;We are not out of the woods yet though. We still have to access the data. And
I found another reason to&amp;nbsp;use &lt;code&gt;unsafe&lt;/code&gt; here:&lt;/p&gt;
&lt;p&gt;Rkyv can be used safe or unsafe. In safe mode it validates the data when loaded.
In unsafe mode it skips that, but you have to ensure the data is valid some other&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;I measured, and I could save a few milliseconds by using the unsafe load. And that
is a sizable fraction of the total program runtime at this point! So what is our
safety invariant here? Quoting the rkyv&amp;nbsp;documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The byte slice must represent a valid archived type when accessed at the
default root position. See the module docs for more&amp;nbsp;information.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The module docs&amp;nbsp;say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The safety requirements for accessing a byte slice will often state that a byte
slice must &amp;ldquo;represent a valid archived type&amp;rdquo;. The specific validity requirements
may vary widely depending on the types being accessed, and so in general the only
way to guarantee that this call is safe is to have previously validated the byte&amp;nbsp;slice.&lt;/p&gt;
&lt;p&gt;Using techniques such as cryptographic signing can provide a more performant way to
verify data integrity from trusted&amp;nbsp;sources.&lt;/p&gt;
&lt;p&gt;It is generally safe to assume that unchanged and properly-aligned serialized bytes
are always safe to access without validation. By contrast, bytes from a
potentially-malicious source should always be validated prior to&amp;nbsp;access.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, what are we trying to protect against? As I hinted at above, since the file
is owned by root&amp;nbsp;in &lt;code&gt;/var/cache/filkoll&lt;/code&gt;, I don&amp;rsquo;t consider malicious actors. If
root is compromised there are bigger problems! So what do we need to protect&amp;nbsp;against?&lt;/p&gt;
&lt;p&gt;The answer is version upgrades! Maybe we change the file format in a new release.
Or update to an incompatible version of rkyv? To deal with this I added a file header
in front of the rkyv&amp;nbsp;data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#[derive(zerocopy::IntoBytes, zerocopy::FromBytes, zerocopy::Immutable, zerocopy::KnownLayout)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// A magic number to identify the file format&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;magic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// INVARIANT: A version number that is manually incremented if the format&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// changes in ways the the hash cannot catch.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;/// A hash of the type of the root object&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;type_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is a lot going on here, lets break it&amp;nbsp;down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First a magic number to identify the file format. This is a good practise in general for binary files
  and can help identify the file. I&amp;nbsp;used &lt;code&gt;0x70757976&lt;/code&gt; (&lt;span class="caps"&gt;FKOL&lt;/span&gt; in &lt;span class="caps"&gt;ASCII&lt;/span&gt;). This does not provide much safety
  though. Just that the file is &lt;em&gt;probably&lt;/em&gt; from the same&amp;nbsp;program.&lt;/li&gt;
&lt;li&gt;Then a format version number that is manually handled. Good, but there is a risk of me forgetting
  this when making changes. But it was needed to handle some edge cases that the final component couldn&amp;rsquo;t&amp;nbsp;handle.&lt;/li&gt;
&lt;li&gt;And, the final part is a hash computed using &lt;a href="https://crates.io/crates/type_hash"&gt;type_hash&lt;/a&gt;. This gives a hash on the data format.
  A hash for&amp;nbsp;the &lt;code&gt;Cargo.lock&lt;/code&gt; file is also mixed into that (to protect against rkyv upgrades etc).
  This includes the specific versions and checksums of all&amp;nbsp;dependencies.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That should cover all eventualities, though it will have false positives (report a mismatch even if
when the format is compatible). But for a cache file that is fine. It is cheap to re-create. And better
safe than&amp;nbsp;sorry.&lt;/p&gt;
&lt;p&gt;So we check the header, and only &lt;em&gt;then&lt;/em&gt; load the rest of the file with rkyv&amp;rsquo;s unsafe accessor&amp;nbsp;function.&lt;/p&gt;
&lt;h3 id="actually-searching-the-cache"&gt;Actually searching the&amp;nbsp;cache&lt;/h3&gt;
&lt;p&gt;Finally, it is time to&amp;nbsp;search!&lt;/p&gt;
&lt;p&gt;Recall&amp;nbsp;from &lt;code&gt;DataRoot&lt;/code&gt; above that we&amp;nbsp;have &lt;code&gt;binaries: HashMap&amp;lt;String, SmallVec&amp;lt;[Record; 2]&amp;gt;&amp;gt;&lt;/code&gt;.
A hash map! We can just look up the binary name. Then resolve the interned strings and print out
the match. And that is exactly what we do for an &lt;em&gt;exact&lt;/em&gt;&amp;nbsp;search.&lt;/p&gt;
&lt;h3 id="fuzzy-searching"&gt;Fuzzy&amp;nbsp;searching&lt;/h3&gt;
&lt;p&gt;Fuzzy searching is a bit more complex. After much looking around and experimenting I used &lt;a href="https://docs.rs/strsim/"&gt;strsim&lt;/a&gt;
for this. This is a library to compute the &amp;ldquo;edit distance&amp;rdquo; between two&amp;nbsp;strings.&lt;/p&gt;
&lt;p&gt;A simple example of a distance function could be &amp;ldquo;how many letters differ between
the two strings&amp;rdquo;. This is known as the &lt;a href="https://en.wikipedia.org/wiki/Hamming_distance"&gt;Hamming distance&lt;/a&gt;.
However, that can only handle strings of the same length. The
&lt;a href="https://en.wikipedia.org/wiki/Levenshtein_distance"&gt;Levenshtein distance&lt;/a&gt;
is a bit more general: it computes the minimum number of insertions, deletions,
or substitutions needed to get from one string to the other. There are many
more, that might handle e.g. &amp;ldquo;swapping&amp;rdquo; as a single edit or other features. However,
after testing a few different ones I settled on the Levenshtein distance as it
was both fast and gave good&amp;nbsp;results.&lt;/p&gt;
&lt;p&gt;However, this approach (comparing string distances) means I need to go through
&lt;em&gt;all&lt;/em&gt; keys in the hash map. I was planning to do something fancier to begin with
(there is&amp;nbsp;a &lt;code&gt;symspell&lt;/code&gt; crate that looked interesting, but also unmaintained).
But the naive approach here was fast enough that I just settled for&amp;nbsp;that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For each key in the hash&amp;nbsp;map:&lt;/li&gt;
&lt;li&gt;Compute the Levenshtein distance to the search string (the typoed&amp;nbsp;binary).&lt;/li&gt;
&lt;li&gt;If it is below a threshold, consider it a potential&amp;nbsp;match&lt;/li&gt;
&lt;li&gt;Sort the potential matches by Levenshtein&amp;nbsp;distance&lt;/li&gt;
&lt;li&gt;If there is an exact match just print it, otherwise print all possible&amp;nbsp;matches.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="testing_2"&gt;Testing&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ll admit: I have a tendency to just write tests for some tricky bits and leave the rest to manual
exploration. Then I throw an integration test on it after while when it becomes difficult to
maintain without one. This actually works surprisingly well for small command line tools like&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;However, writing this article spurred me to do the integration test&amp;nbsp;early.&lt;/p&gt;
&lt;p&gt;What I did is set up a podman container with an Arch Linux image and run a
cache update followed by some test queries in it. Then I check the output against
a &amp;ldquo;golden file&amp;rdquo; and report a failure if it differs. It is simple but&amp;nbsp;effective.&lt;/p&gt;
&lt;p&gt;Though you need to ensure you use a fixed version of the image and package database,
as upgrades can definitely change small details in the output. Thankfully,
Arch Linux has an archive where you can get the sync database for a specific date,
making this sort of testing&amp;nbsp;easy.&lt;/p&gt;
&lt;h1 id="benchmarking-and-profiling"&gt;Benchmarking and&amp;nbsp;profiling&lt;/h1&gt;
&lt;p&gt;And finally: your program isn&amp;rsquo;t fast unless you can prove it is. And this
is where benchmarking and profiling comes in. Much has been written on this
topic already, so there isn&amp;rsquo;t much new I can add. Thus, I&amp;rsquo;ll be brief. Really,
you should just go read the excellent free &lt;a href="https://nnethercote.github.io/perf-book/"&gt;Rust Performance Book&lt;/a&gt;&amp;nbsp;instead.&lt;/p&gt;
&lt;p&gt;For command line programs with a fixed runtime (like the program this blog post
is about) you should use &lt;a href="https://github.com/sharkdp/hyperfine"&gt;hyperfine&lt;/a&gt; to
compare the runtime of two different programs under test. This can be your program
vs what came before. Or two copies of your program with slightly different
implementations of an algorithm. If less readable code doesn&amp;rsquo;t make it measurably
faster, it isn&amp;rsquo;t worth it. You can also compare different flags, etc of course as&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;But how do you identify your hotspots so you know &lt;em&gt;where&lt;/em&gt; to try to tweak the code?
Well, you use a profiler. For Linux (which is the only platform I know),&amp;nbsp;use
&lt;code&gt;perf&lt;/code&gt; to record the data. Then I recommend &lt;a href="https://github.com/KDAB/hotspot"&gt;hotspot&lt;/a&gt;
for visualising the data. Every person swears that their favourite visualiser is
the best / easeist / most powerful. I found hotspot to most clearly show me where
the problems are. It not only have flamegraphs, top down views, calle-caller views,
etc, but also a time axis. It is easy to zoom and filter in on different
phases of execution, etc. Hotspot probably isn&amp;rsquo;t the easiest to get up and going with
though, since it has a lot of features. But I use it for both my hobby projects
and for my day job. It gets the job done, and done&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;And then you need to consider memory usage too. For Rust the best option is
&lt;a href="https://github.com/koute/bytehound"&gt;bytehound&lt;/a&gt;, though it takes a bit of work
figuring out the &lt;span class="caps"&gt;UI&lt;/span&gt;. Tip: Try right-clicking on various things that you don&amp;rsquo;t
expect to be right clickable! &lt;a href="https://github.com/KDE/heaptrack"&gt;Heaptrack&lt;/a&gt; is
another option, that is good, but doesn&amp;rsquo;t know how to demangle Rust symbols.
If you are heap profiling C or C++ it is however an excellent choice, with a
&lt;span class="caps"&gt;UI&lt;/span&gt; very similar to Hotspot (though Bytehound is more powerful for advanced
analysis, it is&amp;nbsp;scriptable!).&lt;/p&gt;
&lt;p&gt;Just go and try out a few different tools and see what you like. And read the
&lt;a href="https://nnethercote.github.io/perf-book/"&gt;Rust Performance Book&lt;/a&gt;.&amp;nbsp;Seriously.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:compiles"&gt;
&lt;p&gt;Note that when I say &amp;ldquo;compiles&amp;rdquo; about regular expressions, I refer to the regex being compiled into a state machine at runtime of the Rust program, not compilation of a Rust program itself.&amp;nbsp;&lt;a class="footnote-backref" href="#fnref:compiles" title="Jump back to footnote 1 in the text"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="Programming"></category><category term="Arch Linux"></category><category term="Rust"></category></entry><entry><title>Reverse engineering ACPI functionality on a Toshiba Z830 Ultrabook</title><link href="https://vorpal.se/posts/2022/aug/21/reverse-engineering-acpi-functionality-on-a-toshiba-z830-ultrabook/" rel="alternate"></link><published>2022-08-21T00:00:00+02:00</published><updated>2022-08-21T00:00:00+02:00</updated><author><name>Arvid Norlander</name></author><id>tag:vorpal.se,2022-08-21:/posts/2022/aug/21/reverse-engineering-acpi-functionality-on-a-toshiba-z830-ultrabook/</id><summary type="html">&lt;div&gt;
&lt;a href="https://vorpal.se/images/toshiba_z830/laptop.avif"&gt;
&lt;img alt="The Toshiba Satellite Z830-10W Ultrabook laptop" class="right" loading="lazy" src="https://vorpal.se/images/toshiba_z830/laptop.avif" width="45%"/&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;I have inherited a Toshiba Satellite Z830-10W ultrabook. This is a very light
and small laptop from around 2011. It had Windows 7 on it. With only 4 &lt;span class="caps"&gt;GB&lt;/span&gt; &lt;span class="caps"&gt;RAM&lt;/span&gt;
and a 128 &lt;span class="caps"&gt;GB&lt;/span&gt; mSATA &lt;span class="caps"&gt;SSD&lt;/span&gt;, it is a bit on the weak side for running Windows 10.
Plus I …&lt;/p&gt;</summary><content type="html">&lt;div&gt;
&lt;a href="https://vorpal.se/images/toshiba_z830/laptop.avif"&gt;
&lt;img alt="The Toshiba Satellite Z830-10W Ultrabook laptop" class="right" loading="lazy" src="https://vorpal.se/images/toshiba_z830/laptop.avif" width="45%"/&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;I have inherited a Toshiba Satellite Z830-10W ultrabook. This is a very light
and small laptop from around 2011. It had Windows 7 on it. With only 4 &lt;span class="caps"&gt;GB&lt;/span&gt; &lt;span class="caps"&gt;RAM&lt;/span&gt;
and a 128 &lt;span class="caps"&gt;GB&lt;/span&gt; mSATA &lt;span class="caps"&gt;SSD&lt;/span&gt;, it is a bit on the weak side for running Windows 10.
Plus I&amp;rsquo;m a Linux user. However, there were issues under&amp;nbsp;Linux.&lt;/p&gt;
&lt;h1 id="initial-investigation"&gt;Initial&amp;nbsp;investigation&lt;/h1&gt;
&lt;p&gt;Since the form factor and weight was nice (and the battery still had life in it)
I decided it might be worth experimenting with. So I booted a &lt;span class="caps"&gt;USB&lt;/span&gt; stick with
Xubuntu (not my choice to install, but quick to test out basic features from
the live environment). I found several things that did not work: some LEDs,
some buttons and backlight breaking after a sleep and resume&amp;nbsp;cycle.&lt;/p&gt;
&lt;p&gt;Some may be annoyed at this, but I have been interested in getting into reverse
engineering and kernel programming for a long time, so I saw this as an
opportunity and good first problem rather than a&amp;nbsp;problem.&lt;/p&gt;
&lt;p&gt;At this point I made a list of the problem I knew of so far. I also set up an
external &lt;span class="caps"&gt;SSD&lt;/span&gt; with Linux as 128 &lt;span class="caps"&gt;GB&lt;/span&gt; is not enough to work comfortably with both
Windows and Linux, and I would need to trace things under Windows to see how
they work. I used &lt;a href="https://archlinux.org/"&gt;Arch Linux&lt;/a&gt; as that is my Linux distro of choice. The laptop
has one &lt;span class="caps"&gt;USB&lt;/span&gt; 3 port, making this approach&amp;nbsp;bearable.&lt;/p&gt;
&lt;p&gt;Then I spend some time reading the Linux kernel driver &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/platform/x86/toshiba_acpi.c?h=v5.19.2"&gt;toshiba_acpi&lt;/a&gt; that
provided some working features to familiarise myself with how &lt;span class="caps"&gt;ACPI&lt;/span&gt; on Toshiba
laptops&amp;nbsp;work.&lt;/p&gt;
&lt;h2 id="acpi"&gt;&lt;span class="caps"&gt;ACPI&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;A quick summary of &lt;a href="https://en.wikipedia.org/wiki/Advanced_Configuration_and_Power_Interface"&gt;&lt;span class="caps"&gt;ACPI&lt;/span&gt;&lt;/a&gt; is in order at this point: It is a standard
(originally introduced in 1996) that lets the firmware the features of the
hardware to the operating system. It is focused on describing things that can
not be auto discovered and on power management. For&amp;nbsp;example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Addresses of memory mapped chips on the motherboard, such as the &lt;span class="caps"&gt;CMOS&lt;/span&gt;&amp;nbsp;clock.&lt;/li&gt;
&lt;li&gt;How to suspend and turn off the&amp;nbsp;computer.&lt;/li&gt;
&lt;li&gt;Notifications of the lid closing or opening on a&amp;nbsp;laptop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It also supports vendor specific extensions, and &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/platform/x86/toshiba_acpi.c?h=v5.19.2"&gt;toshiba_acpi&lt;/a&gt; implements
support for those under Linux. I spent some time reading up on &lt;span class="caps"&gt;ACPI&lt;/span&gt; as well.
Some basic functionality is described in fixed format tables. However, most
of the complicated features is described by a program that is provided as byte
code to the &lt;span class="caps"&gt;OS&lt;/span&gt;. The &lt;span class="caps"&gt;OS&lt;/span&gt; implements a virtual machine that runs the &lt;span class="caps"&gt;ACPI&lt;/span&gt; byte&amp;nbsp;code.&lt;/p&gt;
&lt;h1 id="tooling-under-windows_1"&gt;Tooling under&amp;nbsp;Windows&lt;/h1&gt;
&lt;p&gt;After reading up on relevant background material I needed to figure out what
tooling to use on Windows. One thing I quickly discovered was the &lt;a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/the-amli-debugger"&gt;&lt;span class="caps"&gt;AMLI&lt;/span&gt;&lt;/a&gt;
debugger. This is bundled with Debugging Tools for Windows and lets you trace
&lt;span class="caps"&gt;ACPI&lt;/span&gt; calls when kernel debugging. One snag though: On Windows 7 it needs a
&amp;ldquo;checked build&amp;rdquo; of the operating system, specifically&amp;nbsp;of &lt;code&gt;ACPI.SYS&lt;/code&gt;. I tried to
get this working, it did not work&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;I ended up upgrading the laptop to Windows 10. While that is a much worse
experience (the laptop takes minutes after boot to become usable, it is
completely unusable while updates are downloading or installing, &amp;hellip;) I do not
plan to keep Windows 10 (or Windows in general) long term. For now, it&amp;nbsp;works.&lt;/p&gt;
&lt;p&gt;Using a local kernel debugging session on the Toshiba I was able to&amp;nbsp;execute
&lt;code&gt;!amli set traceon spewon&lt;/code&gt;. This makes it output a lot of debug info&amp;nbsp;to
&lt;code&gt;!dbgprint&lt;/code&gt;. A lot. This was way too much to do my intended &amp;ldquo;press a button,
see what happens&amp;rdquo; approach. I needed to log this to file and compare traces to
make any headway finding the needles in the&amp;nbsp;haystacks.&lt;/p&gt;
&lt;p&gt;Sysinternals to the rescue! &lt;a href="https://docs.microsoft.com/en-us/sysinternals/downloads/debugview"&gt;DebugView&lt;/a&gt; is a program in the Sysinternals suite
of programs that lets me do exactly that (amongst other&amp;nbsp;features).&lt;/p&gt;
&lt;h2 id="side-note-user-space-tracing"&gt;Side note: User space&amp;nbsp;tracing&lt;/h2&gt;
&lt;p&gt;I did also investigate the possibility of user space tracing, but that would
look at the &lt;span class="caps"&gt;API&lt;/span&gt; between the user space programs and the kernel, which &lt;em&gt;might&lt;/em&gt;
be relevant, but might use a completely different &lt;span class="caps"&gt;API&lt;/span&gt; that is being translated
in the driver. Some tools I came across that might be useful if this is what you
want to&amp;nbsp;do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.rohitab.com/apimonitor"&gt;&lt;span class="caps"&gt;API&lt;/span&gt; Monitor&lt;/a&gt; seems to allow tracing
  a number of library calls and system calls. In some ways it is comparable to
  &lt;a href="https://strace.io/"&gt;strace&lt;/a&gt; on&amp;nbsp;Linux.&lt;/li&gt;
&lt;li&gt;Spy++ is a tool to monitor window messages. It is a part of Microsoft Visual
  Studio. This might be useful as a&amp;nbsp;complement.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the end I had a brief look at these tools but did not end up using them
for my actual reverse engineering work other than to determine which user space
programs and services were talking to the driver (so I could kill all but the
one I wanted to investigate at the time and reduce noise). This was really
needed as especially the program that pop up an overlay for when you press the
Fn-key tended to perform a lot of repeated background queries for the same thing
over and over again. And then there is the &lt;span class="caps"&gt;ECO&lt;/span&gt; process that keeps querying for
what I believe is power usage all the time (as that seems to be the only thing
it is used for). Both of these seem a bit wasteful on the battery in my&amp;nbsp;eyes.&lt;/p&gt;
&lt;p&gt;Interestingly, the handling of the &lt;a href="#hardware-buttons"&gt;extra buttons&lt;/a&gt; below by
user space was not done via the usual method&amp;nbsp;of &lt;code&gt;DeviceIoControl&lt;/code&gt; calls.
Instead, this was received as Window event notifications of&amp;nbsp;the
&lt;code&gt;WM_POWERBROADCAST&lt;/code&gt; type.&lt;/p&gt;
&lt;p&gt;The Toshiba driver on for &lt;span class="caps"&gt;ACPI&lt;/span&gt; on Windows is&amp;nbsp;named &lt;code&gt;TVALZ.sys&lt;/code&gt; by the way (at
least on this particular laptop) in case you want to figure out which processes
opens handles to&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="the-process-reverse-engineering"&gt;The process: reverse&amp;nbsp;engineering&lt;/h2&gt;
&lt;p&gt;Next came a tedious task of establishing a baseline with all programs stopped.
This involved grepping the various logs on a Linux computer. Then I just had to
look at the new logs excluding all those things I filtered out. Each single &lt;span class="caps"&gt;ACPI&lt;/span&gt;
function call typically generates hundreds of lines in the log, with details
about function parameters and sub calls. Fortunately each new entry point is
marked with the&amp;nbsp;line &lt;code&gt;AMLI:&lt;/code&gt;. I have included part of one call below as an
example. This particular call queries the state of the keyboard backlight. The
full log for this call is 168 lines, and as such I have left it out of&amp;nbsp;here!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;00015647    10:09:48    AMLI: FFFFD082FA3A2080: \_SB.VALZ.GHCI(Buffer(0x4){ 
00015648    10:09:48     0x00,0xfe,0x00,0x00},Buffer(0x4){ 
00015649    10:09:48     0x95,0x00,0x00,0x00},Buffer(0x4){ 
00015650    10:09:48     0x00,0x00,0x00,0x00},Buffer(0x4){ 
00015651    10:09:48     0x00,0x00,0x00,0x00},Buffer(0x4){ 
00015652    10:09:48     0x00,0x00,0x00,0x00},Buffer(0x4){ 
00015653    10:09:48     0x00,0x00,0x00,0x00})
00015654    10:09:48     
00015655    10:09:48    ffffd082f388c1aa: { 
00015656    10:09:48    ffffd082f388c1aa: CreateDWordField(Arg0=Buffer(0x4){ 
00015657    10:09:48     0x00,0xfe,0x00,0x00},Zero,REAX) 
00015658    10:09:48    ffffd082f388c1b1: CreateWordField(Arg1=Buffer(0x4){ 
00015659    10:09:48     0x95,0x00,0x00,0x00},Zero,R_BX) 
00015660    10:09:48    ffffd082f388c1b8: And(REAX,0xff00,Local0)=0xfe00 
00015661    10:09:48    ffffd082f388c1c1: If(LEqual(Local0=0xfe00,0xfe00)=0xffffffffffffffff) 
00015662    10:09:48    ffffd082f388c1c9: { 
00015663    10:09:48    ffffd082f388c1c9: If(LEqual(R_BX,0xc000)=0x0) 
00015664    10:09:48    ffffd082f388c1e1: If(LEqual(R_BX,0xc800)=0x0) 
00015665    10:09:48    ffffd082f388c1f9: If(LEqual(R_BX,0xc801)=0x0) 
00015666    10:09:48    ffffd082f388c211: } 
00015667    10:09:48    ffffd082f388c211: If(LEqual(Local0=0xfe00,0xff00)=0x0) 
00015668    10:09:48    ffffd082f388c248: Return(GCH0(Arg0=Buffer(0x4){ 
00015669    10:09:48     0x00,0xfe,0x00,0x00},Arg1=Buffer(0x4){
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The log consists of line number, time stamp and log text. The log text contains
some decompiled &lt;span class="caps"&gt;ACPI&lt;/span&gt; byte code (known as &lt;span class="caps"&gt;ACPI&lt;/span&gt; Machine Language or &lt;span class="caps"&gt;AML&lt;/span&gt; for
short). &lt;span class="caps"&gt;AML&lt;/span&gt; defines both a nested structure of objects and methods, but also the
instructions in those&amp;nbsp;methods.&lt;/p&gt;
&lt;h2 id="the-process-testing-your-hypotheses"&gt;The process: testing your&amp;nbsp;hypotheses&lt;/h2&gt;
&lt;p&gt;Once I had a set of hypotheses based on the reverse engineering I had done I
needed to test them. One way would be to actually change to Linux kernel driver,
recompile it and then reload it&amp;nbsp;via &lt;code&gt;rmmod&lt;/code&gt; and &lt;code&gt;insmod&lt;/code&gt; (or&amp;nbsp;using &lt;code&gt;modprobe&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;However, a much quicker option is to use &lt;a href="https://github.com/mkottman/acpi_call"&gt;acpi_call&lt;/a&gt;. This is a out of tree
kernel module that allows the root user to do direct &lt;span class="caps"&gt;ACPI&lt;/span&gt; method calls from the
comfort of their own shell prompt. I ended up using the following helper
functions in&amp;nbsp;my &lt;code&gt;zsh&lt;/code&gt; prompt to test&amp;nbsp;these:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;call_ghci&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"\\_SB_.VALZ.GHCI &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;6&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;/proc/acpi/call
&lt;span class="w"&gt;  &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/proc/acpi/call&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

set_hci&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;call_ghci&lt;span class="w"&gt; &lt;/span&gt;0xff00&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
get_hci&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;call_ghci&lt;span class="w"&gt; &lt;/span&gt;0xfe00&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
get_sci&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;call_ghci&lt;span class="w"&gt; &lt;/span&gt;0xf300&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
set_sci&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;call_ghci&lt;span class="w"&gt; &lt;/span&gt;0xf400&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
open_sci&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;call_ghci&lt;span class="w"&gt; &lt;/span&gt;0xf100&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
close_sci&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;call_ghci&lt;span class="w"&gt; &lt;/span&gt;0xf200&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Some example&amp;nbsp;usages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="c1"&gt;# Turn on ECO LED&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;set_hci&lt;span class="w"&gt; &lt;/span&gt;0x97&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="go"&gt;\_SB_.VALZ.GHCI 0xff00 0x97 1 1 0 0&lt;/span&gt;
&lt;span class="go"&gt;[0x0, 0x97, 0x1, 0x1, 0x0, 0x0]&lt;/span&gt;

&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="c1"&gt;# Get the BIOS boot order&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;open_sci&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;get_sci&lt;span class="w"&gt; &lt;/span&gt;0x157&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;close_sci
&lt;span class="go"&gt;\_SB_.VALZ.GHCI 0xf100 0 0 0 0 0&lt;/span&gt;
&lt;span class="go"&gt;[0x44, 0x0, 0x0, 0x0, 0x0, 0x0]&lt;/span&gt;
&lt;span class="go"&gt;\_SB_.VALZ.GHCI 0xf300 0x157 0 0 0 0&lt;/span&gt;
&lt;span class="go"&gt;[0x0, 0x8505, 0xfff30174, 0x5, 0xfff30741, 0x0]&lt;/span&gt;
&lt;span class="go"&gt;\_SB_.VALZ.GHCI 0xf200 0 0 0 0 0&lt;/span&gt;
&lt;span class="go"&gt;[0x44, 0x0, 0x0, 0x0, 0x0, 0x0]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This allowed me to test almost all the features without changing kernel code.
The one exception is the notifications for &lt;a href="#hardware-buttons"&gt;the&amp;nbsp;buttons&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Some other tooling that may be useful to know about under Linux is that many
(but not all) &lt;span class="caps"&gt;ACPI&lt;/span&gt; events are sent to user space via &lt;a href="https://en.wikipedia.org/wiki/Netlink"&gt;Netlink&lt;/a&gt;. I found that
the &lt;a href="https://github.com/svinota/pyroute2"&gt;pyroute2&lt;/a&gt; library allowed me to read this without resorting to coding in&amp;nbsp;C.&lt;/p&gt;
&lt;p&gt;The documentation was lacking for this particular feature&amp;nbsp;of &lt;code&gt;pyroute2&lt;/code&gt;, but
that has &lt;a href="https://github.com/svinota/pyroute2/issues/1001"&gt;since been fixed&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the end reading the &lt;span class="caps"&gt;ACPI&lt;/span&gt; events over netlink was not useful to me. The things
I needed were not&amp;nbsp;there.&lt;/p&gt;
&lt;h1 id="results_1"&gt;Results&lt;/h1&gt;
&lt;p&gt;In this section I present my findings. This is organised to help someone who
wants to work on this, or perhaps extend my work. It is &lt;em&gt;not&lt;/em&gt; for a general
audience, but is intended as reference material for anyone working on kernel
development in this area, or intending to improve Linux support for similar&amp;nbsp;devices.&lt;/p&gt;
&lt;h2 id="background-on-toshiba-acpi-communication-methods"&gt;Background on Toshiba &lt;span class="caps"&gt;ACPI&lt;/span&gt; communication&amp;nbsp;methods&lt;/h2&gt;
&lt;p&gt;This section is a short summary of the general protocol. This is already
implemented in the &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/platform/x86/toshiba_acpi.c?h=v5.19.2"&gt;toshiba_acpi&lt;/a&gt; Linux kernel driver. If you are already
familiar with that you can skip this&amp;nbsp;section.&lt;/p&gt;
&lt;p&gt;Almost all vendor specific features work via&amp;nbsp;the &lt;code&gt;\_SB_.VALZ&lt;/code&gt; &lt;span class="caps"&gt;ACPI&lt;/span&gt; device
defined in the &lt;span class="caps"&gt;DSDT&lt;/span&gt;. This device is also known&amp;nbsp;as &lt;code&gt;TOS6208&lt;/code&gt; and &lt;code&gt;VALZeneral&lt;/code&gt;.
There are a handful of interesting methods on this object, but for the purposes
of this write-up&amp;nbsp;only &lt;code&gt;GHCI&lt;/code&gt; is relevant. This method takes 6 integer (32-bit)
arguments and returns a buffer 6 32-bit&amp;nbsp;integers.&lt;/p&gt;
&lt;p&gt;The general format of queries&amp;nbsp;is: &lt;code&gt;{OPERATION, REGISTER, ARG1, ..., ARG4 }&lt;/code&gt;. The
operation is one&amp;nbsp;of &lt;code&gt;HCI_GET&lt;/code&gt;/&lt;code&gt;HCI_SET&lt;/code&gt; or &lt;code&gt;SCI_GET&lt;/code&gt;/&lt;code&gt;SCI_SET&lt;/code&gt; (plus &lt;code&gt;SCI_OPEN&lt;/code&gt;
and &lt;code&gt;SCI_CLOSE&lt;/code&gt;). This allows for getting and setting various registers to
control features or read out&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;The data returned varies a bit, but is generally on the&amp;nbsp;form:
&lt;code&gt;{STATUS_CODE, REGISTER_FROM_QUERY, VAL1, ..., VAL4 }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;What is the difference&amp;nbsp;between &lt;code&gt;HCI_*&lt;/code&gt; and &lt;code&gt;SCI_*&lt;/code&gt; calls? The only important
difference here is that&amp;nbsp;for &lt;code&gt;SCI_GET&lt;/code&gt;/&lt;code&gt;SCI_SET&lt;/code&gt; you first need to&amp;nbsp;call
&lt;code&gt;SCI_OPEN&lt;/code&gt; and then follow the get or set with&amp;nbsp;a &lt;code&gt;SCI_CLOSE&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;Much of the rest of this write-up consists of documenting registers previously
not handled by&amp;nbsp;the &lt;code&gt;toshiba_acpi&lt;/code&gt; Linux&amp;nbsp;driver.&lt;/p&gt;
&lt;h2 id="the-eco-led"&gt;The &amp;ldquo;Eco&amp;rdquo; &lt;span class="caps"&gt;LED&lt;/span&gt;&lt;/h2&gt;
&lt;div&gt;
&lt;a href="https://vorpal.se/images/toshiba_z830/leds.avif"&gt;
&lt;img alt="LEDs on the laptop, below the touchpad" class="right" loading="lazy" src="https://vorpal.se/images/toshiba_z830/leds.avif" title="LEDs on the laptop, below the touchpad" width="45%"/&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;toshiba_acpi&lt;/code&gt; driver has support for controlling some LEDs including the
&amp;ldquo;Eco&amp;rdquo; &lt;span class="caps"&gt;LED&lt;/span&gt; (the eco &lt;span class="caps"&gt;LED&lt;/span&gt; is the one on the far right in the picture).
Unfortunately that &lt;span class="caps"&gt;LED&lt;/span&gt; works differently on this&amp;nbsp;laptop.&lt;/p&gt;
&lt;p&gt;Apparently Toshiba has two formats for controlling this on differet&amp;nbsp;laptops:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// Format A&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HCI_SET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HCI_ECO_MODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;onoff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Format B&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HCI_SET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HCI_ECO_MODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;onoff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;toshiba_acpi&lt;/code&gt; driver tries to use format B if it gets the&amp;nbsp;error
&lt;code&gt;TOS_INPUT_DATA_ERROR&lt;/code&gt; when trying to use format A. On this laptop the error
returned&amp;nbsp;is &lt;code&gt;TOS_NOT_SUPPORTED&lt;/code&gt;. Other than that format B works as&amp;nbsp;expected.&lt;/p&gt;
&lt;h2 id="battery-charge-mode"&gt;Battery charge&amp;nbsp;mode&lt;/h2&gt;
&lt;p&gt;This laptop supports not charging the battery fully in order to prolong battery
life. Unlike for example ThinkPads where this control is granular here it is
just off/on. When off it charges to 100%. When on it charges to about&amp;nbsp;80%.&lt;/p&gt;
&lt;p&gt;According to the Windows program used to control the feature the setting will
not take effect until the battery has been discharged to around 50%. On Windows
Toshiba branded this feature as &amp;ldquo;Eco&amp;nbsp;charging&amp;rdquo;&lt;/p&gt;
&lt;p&gt;In the following example &lt;span class="caps"&gt;ACPI&lt;/span&gt; calls I will use the following newly defined&amp;nbsp;constants:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define HCI_BATTERY_CHARGE_MODE 0xba&lt;/span&gt;
&lt;span class="cp"&gt;#define BATTERY_CHARGE_FULL 0&lt;/span&gt;
&lt;span class="cp"&gt;#define BATTERY_CHARGE_80_PERCENT 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;To set the&amp;nbsp;feature:
  &lt;code&gt;{HCI_SET, HCI_BATTERY_CHARGE_MODE, charge_mode, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To query for the existence of the&amp;nbsp;feature:
  &lt;code&gt;{HCI_GET, HCI_BATTERY_CHARGE_MODE, 0, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To read the&amp;nbsp;feature:
  &lt;code&gt;{HCI_GET, HCI_BATTERY_CHARGE_MODE, 0, 0, 0, 1}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The read may need to be retried&amp;nbsp;if &lt;code&gt;TOS_DATA_NOT_AVAILABLE&lt;/code&gt; is returned as the
status code. This rarely happens (never observed it on Linux), but I have seen
it happen under Windows, and the Windows software it did retry&amp;nbsp;it.&lt;/p&gt;
&lt;h2 id="panel-power-control-via-hci"&gt;Panel power control via &lt;span class="caps"&gt;HCI&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;toshiba_acpi&lt;/code&gt; driver supports controlling the panel power via &lt;span class="caps"&gt;SCI&lt;/span&gt; calls
(SCI_PANEL_POWER_ON). This laptop appears to support that (the codes give no
errors), but nothing happens. Instead, &lt;span class="caps"&gt;HCI&lt;/span&gt; calls must be&amp;nbsp;used.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define HCI_PANEL_POWER_ON 0x2&lt;/span&gt;
&lt;span class="cp"&gt;#define PANEL_ON 1&lt;/span&gt;
&lt;span class="cp"&gt;#define PANEL_OFF 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;To read/query&amp;nbsp;existence: &lt;code&gt;{HCI_GET, HCI_PANEL_POWER_ON, 0, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To&amp;nbsp;write: &lt;code&gt;{HCI_SET, HCI_PANEL_POWER_ON, panel_on, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hardware-buttons"&gt;Hardware&amp;nbsp;buttons&lt;/h2&gt;
&lt;p&gt;All&amp;nbsp;the &lt;code&gt;Fn+&amp;lt;key&amp;gt;&lt;/code&gt; hotkeys work. However, there are some hardware buttons that
do not. These buttons&amp;nbsp;are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A button between space and the touchpad to turn off/on the&amp;nbsp;touchpad.&lt;/li&gt;
&lt;li&gt;Two buttons next to the power button, one is &amp;ldquo;eco-mode&amp;rdquo;, the other is&amp;nbsp;&amp;ldquo;projector&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="img-row"&gt;
&lt;div class="img-column2"&gt;
&lt;a href="https://vorpal.se/images/toshiba_z830/btn_eco_projector.avif"&gt;
&lt;img alt="Eco and Projector Buttons" loading="lazy" src="https://vorpal.se/images/toshiba_z830/btn_eco_projector.avif" title="Eco and Projector Buttons" width="100%"/&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;div class="img-column2"&gt;
&lt;a href="https://vorpal.se/images/toshiba_z830/btn_touchpad_control.avif"&gt;
&lt;img alt="Touchpad On/Off Buttons" loading="lazy" src="https://vorpal.se/images/toshiba_z830/btn_touchpad_control.avif" title="Touchpad On/Off Buttons" width="100%"/&gt;
&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The two buttons next to the power button both send Windows+X by default. The
touchpad control button does nothing that Linux can&amp;nbsp;detect.&lt;/p&gt;
&lt;p&gt;To enable this functionality several changes are&amp;nbsp;needed.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;toshiba_acpi&lt;/code&gt; driver currently&amp;nbsp;uses
  &lt;code&gt;{HCI_SET, HCI_HOTKEY_EVENT, HCI_HOTKEY_ENABLE, 0, ...}&lt;/code&gt;
to enable&amp;nbsp;the &lt;code&gt;Fn+&amp;lt;key&amp;gt;&lt;/code&gt; hotkeys,&amp;nbsp;where &lt;code&gt;HCI_HOTKEY_ENABLE = 0x09&lt;/code&gt;. However on
this laptop the&amp;nbsp;value &lt;code&gt;0x05&lt;/code&gt; must be used&amp;nbsp;instead.&lt;/p&gt;
&lt;p&gt;This is not the whole story however, as these keys do not work like any of the
Fn-hotkeys (&lt;span class="caps"&gt;ACPI&lt;/span&gt; notification&amp;nbsp;on &lt;code&gt;\_SB_.VALZ&lt;/code&gt;). Instead, once enabled via the
above method they start sending notifications on&amp;nbsp;various &lt;code&gt;PNP0C32&lt;/code&gt; devices.
These are currently not handled by Linux. According to a
&lt;a href="https://uefi.org/PNP_ACPI_Registry"&gt;search&lt;/a&gt; &lt;code&gt;PNP0C32&lt;/code&gt; is &amp;ldquo;&lt;span class="caps"&gt;HIDACPI&lt;/span&gt; Button&amp;nbsp;Device&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The devices in question&amp;nbsp;are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PNP0C32 \_SB_.HS81 UID 0x03&lt;/code&gt;: Enable/disable&amp;nbsp;trackpad&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PNP0C32 \_SB_.HS87 UID 0x01&lt;/code&gt;: Eco&amp;nbsp;button&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PNP0C32 \_SB_.HS86 UID 0x02&lt;/code&gt;: Monitor/projector&amp;nbsp;button&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only the &amp;ldquo;path&amp;rdquo; and&amp;nbsp;the &lt;code&gt;UID&lt;/code&gt; value in the &lt;span class="caps"&gt;ACPI&lt;/span&gt; &lt;span class="caps"&gt;DSDT&lt;/span&gt; tell these devices&amp;nbsp;apart.&lt;/p&gt;
&lt;p&gt;The notification always uses the&amp;nbsp;value &lt;code&gt;0x80&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="bios-setting-control-from-the-os"&gt;&lt;span class="caps"&gt;BIOS&lt;/span&gt; setting control from the &lt;span class="caps"&gt;OS&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Several of the &lt;span class="caps"&gt;BIOS&lt;/span&gt; settings can be controlled from the &lt;span class="caps"&gt;OS&lt;/span&gt;. This all happens
via &lt;span class="caps"&gt;SCI&lt;/span&gt; calls. On Windows&amp;nbsp;the &lt;code&gt;Hwsetup.exe&lt;/code&gt; program offers this control. I&amp;rsquo;m
not sure how useful any of this is (as this is already available via the &lt;span class="caps"&gt;BIOS&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;Still: it is a neat feature to&amp;nbsp;have.&lt;/p&gt;
&lt;h3 id="setting-boot-order"&gt;Setting boot&amp;nbsp;order&lt;/h3&gt;
&lt;p&gt;This is a &lt;span class="caps"&gt;BIOS&lt;/span&gt; (not &lt;span class="caps"&gt;UEFI&lt;/span&gt;) laptop, so boot order could normally not be controlled
from the &lt;span class="caps"&gt;OS&lt;/span&gt;. However here it is&amp;nbsp;possible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define SCI_BOOT_ORDER 0x157&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this &lt;span class="caps"&gt;SCI&lt;/span&gt; register the boot order is stored as a list with each nibble
indicating a&amp;nbsp;device:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define SCI_BOOT_ORDER_FDD 0x0&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_BOOT_ORDER_HDD 0x1&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_BOOT_ORDER_LAN 0x3&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_BOOT_ORDER_USB_MEMORY 0x4&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_BOOT_ORDER_USB_CD 0x7&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_BOOT_ORDER_USB_UNUSED 0xf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These are then combined as&amp;nbsp;follows:&lt;/p&gt;
&lt;p&gt;For example, consider the request to set boot order to &lt;span class="caps"&gt;USB&lt;/span&gt; memory, &lt;span class="caps"&gt;USB&lt;/span&gt; &lt;span class="caps"&gt;CD&lt;/span&gt;, &lt;span class="caps"&gt;HDD&lt;/span&gt;,
&lt;span class="caps"&gt;LAN&lt;/span&gt;, &lt;span class="caps"&gt;FDD&lt;/span&gt;: &lt;code&gt;{SCI_SET, SCI_BOOT_ORDER, 0xfff03174, 0, 0, 0}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Each nibble indicates a device, with the lowest nibble being the first device
in the boot order. As this device doesn&amp;rsquo;t have a physical &lt;span class="caps"&gt;FDD&lt;/span&gt; I assume that
this refers to &lt;span class="caps"&gt;USB&lt;/span&gt; attached devices, but I have not tested this (I do have a
&lt;span class="caps"&gt;USB&lt;/span&gt; floppy drive if anyone really&amp;nbsp;cares).&lt;/p&gt;
&lt;p&gt;When reading the data out the result is a bit&amp;nbsp;surprising:
&lt;code&gt;{0x0, 0x8505, 0xfff30174, 0x5, 0xfff30741, 0x0}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Presumably these other values also mean something, the boot order in this case
is &lt;span class="caps"&gt;USB&lt;/span&gt; memory, &lt;span class="caps"&gt;USB&lt;/span&gt; &lt;span class="caps"&gt;CD&lt;/span&gt;, &lt;span class="caps"&gt;HDD&lt;/span&gt;, &lt;span class="caps"&gt;FDD&lt;/span&gt;, &lt;span class="caps"&gt;LAN&lt;/span&gt;, so the third value is the boot&amp;nbsp;order.&lt;/p&gt;
&lt;h3 id="setting-usb-memory-emulation"&gt;Setting &lt;span class="caps"&gt;USB&lt;/span&gt; memory&amp;nbsp;emulation&lt;/h3&gt;
&lt;p&gt;The &lt;span class="caps"&gt;BIOS&lt;/span&gt; can either treat &lt;span class="caps"&gt;USB&lt;/span&gt; memories as HDDs or FDDs for booting&amp;nbsp;purposes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define SCI_BOOT_FLOPPY_EMULATION 0x511&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_BOOT_FLOPPY_EMULATION_FDD 0x1&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_BOOT_FLOPPY_EMULATION_HDD 0x0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;To&amp;nbsp;set: &lt;code&gt;{SCI_SET, SCI_BOOT_FLOPPY_EMULATION, value, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Getting/existence&amp;nbsp;query: &lt;code&gt;{SCI_GET, SCI_BOOT_FLOPPY_EMULATION, 0, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="display-during-boot"&gt;Display during&amp;nbsp;boot&lt;/h3&gt;
&lt;p&gt;This controls if &lt;span class="caps"&gt;BIOS&lt;/span&gt;/&lt;span class="caps"&gt;GRUB&lt;/span&gt;/etc is shown on just the internal monitor or&amp;nbsp;not.&lt;/p&gt;
&lt;p&gt;Note: When changing this in Windows it tells me a restart is&amp;nbsp;required.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define SCI_BOOT_DISPLAY 0x300&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_BOOT_DISPLAY_INTERNAL 0x1250&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_BOOT_DISPLAY_AUTO 0x3250&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;To&amp;nbsp;set: &lt;code&gt;{SCI_SET, SCI_BOOT_DISPLAY, value, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Getting/existence query as&amp;nbsp;usual.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cpu-control"&gt;&lt;span class="caps"&gt;CPU&lt;/span&gt;&amp;nbsp;control&lt;/h3&gt;
&lt;p&gt;I presume this is only for operating systems that don&amp;rsquo;t manage this themselves,
I don&amp;rsquo;t know for sure. The wording in the documentation is vague, but I believe
it controls &lt;span class="caps"&gt;CPU&lt;/span&gt; frequency&amp;nbsp;behaviour.&lt;/p&gt;
&lt;p&gt;Note: When changing this in Windows it tells me a restart is&amp;nbsp;required.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define SCI_CPU_FREQUENCY 0x132&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_CPU_FREQUENCY_DYNAMIC 0x0&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_CPU_FREQUENCY_HIGH 0x1&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_CPU_FREQUENCY_LOW 0x2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Set and get as&amp;nbsp;usual: &lt;code&gt;{SCI_GET/SET, SCI_CPU_FREQUENCY, value, 0, 0, 0}&lt;/code&gt; (You
should be spotting a pattern by&amp;nbsp;now.)&lt;/p&gt;
&lt;h3 id="wake-on-lan-wol"&gt;Wake on &lt;span class="caps"&gt;LAN&lt;/span&gt;&amp;nbsp;(WoL)&lt;/h3&gt;
&lt;p&gt;Note! This only controls Wake on &lt;span class="caps"&gt;LAN&lt;/span&gt; when off/hibernated (and since this laptop
has Intel Rapid Start, presumably in that mode too). It is not relevant to WoL
when in&amp;nbsp;sleep.&lt;/p&gt;
&lt;p&gt;Here the Windows driver seem to query several possibilities until it hits on
one that&amp;nbsp;works:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define SCI_WAKE_ON_LAN 0x700&lt;/span&gt;

&lt;span class="cp"&gt;#define SCI_WAKE_ON_LAN_OFF 0x1&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_WAKE_ON_LAN_ON 0x1&lt;/span&gt;

&lt;span class="cp"&gt;#define SCI_WAKE_ON_LAN_REG1 0x0&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_WAKE_ON_LAN_REG2 0x1000&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_WAKE_ON_LAN_REG3 0x800&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;To&amp;nbsp;set:
  &lt;code&gt;{SCI_SET, SCI_WAKE_ON_LAN, value | register, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;To&amp;nbsp;get/query:
  &lt;code&gt;{SCI_GET, SCI_WAKE_ON_LAN, register, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example on this specific laptop to enable&amp;nbsp;WoL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SCI_SET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SCI_WAKE_ON_LAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SCI_WAKE_ON_LAN_ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SCI_WAKE_ON_LAN_REG3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;REG1&lt;/code&gt; and &lt;code&gt;REG2&lt;/code&gt; give return&amp;nbsp;code &lt;code&gt;TOS_INPUT_DATA_ERROR&lt;/code&gt; on this laptop, but
presumably they are needed on some laptops, or the Windows program would not be
attempting to use&amp;nbsp;them.&lt;/p&gt;
&lt;h3 id="sata-power-control"&gt;&lt;span class="caps"&gt;SATA&lt;/span&gt; power&amp;nbsp;control&lt;/h3&gt;
&lt;p&gt;This is another one that I don&amp;rsquo;t know what exactly it corresponds to, maybe it
is something Linux can control&amp;nbsp;directly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define SCI_SATA_POWER 0x406&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_SATA_POWER_BATTERY_LIFE 0x1&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_SATA_POWER_PERFORMANCE 0x0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Get/set/query as&amp;nbsp;expected: &lt;code&gt;{SCI_SET, SCI_SATA_POWER, value, 0, 0, 0}&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="legacy-usb"&gt;Legacy &lt;span class="caps"&gt;USB&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Controls Legacy &lt;span class="caps"&gt;USB&lt;/span&gt; support in &lt;span class="caps"&gt;BIOS&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Note: When changing this in Windows it tells me a restart is&amp;nbsp;required.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define SCI_LEGACY_USB 0x50c&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_LEGACY_USB_ENABLED 0x1&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_LEGACY_USB_DISABLED 0x0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Get/set/query as&amp;nbsp;expected: &lt;code&gt;{SCI_SET, SCI_LEGACY_USB, value, 0, 0, 0}&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="wake-on-keyboard"&gt;Wake on&amp;nbsp;keyboard&lt;/h3&gt;
&lt;p&gt;This controls if pressing a key on the keyboard wakes the laptop from sleep.
Otherwise, only opening the monitor or pressing the power button works for&amp;nbsp;this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define SCI_WAKE_ON_KEYBOARD 0x137&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_WAKE_ON_KEYBOARD_ENABLE 0x8&lt;/span&gt;
&lt;span class="cp"&gt;#define SCI_WAKE_ON_KEYBOARD_DISABLE 0x0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Get/set/query as&amp;nbsp;expected: &lt;code&gt;{SCI_SET, SCI_WAKE_ON_KEYBOARD, value, 0, 0, 0}&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="other-features_1"&gt;Other&amp;nbsp;features&lt;/h2&gt;
&lt;p&gt;Here is a summary of other features that I have not been fully able to decode
and&amp;nbsp;understand.&lt;/p&gt;
&lt;h3 id="power-usage"&gt;Power&amp;nbsp;usage&lt;/h3&gt;
&lt;p&gt;The Windows-software can read power usage in watts both when on &lt;span class="caps"&gt;AC&lt;/span&gt; and when on&amp;nbsp;battery.&lt;/p&gt;
&lt;p&gt;On startup of the program for this and when switching between &lt;span class="caps"&gt;AC&lt;/span&gt; and&amp;nbsp;battery:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{HCI_SET, 0x42, 0x1, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{HCI_SET, 0x42, 0x10, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When on &lt;span class="caps"&gt;AC&lt;/span&gt; the following calls are&amp;nbsp;involved:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{HCI_GET, 0xa7, 0x0, 0x0, 0x8b, 0x0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{HCI_GET, 0xa7, 0x0, 0x0, 0x8b, 0x1}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{HCI_GET, 0xa8, 0x1, 0x0, 0x98, 0x0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{HCI_GET, 0xa8, 0x1, 0x0, 0x98, 0x1}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When on battery the calls&amp;nbsp;changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{HCI_GET, 0xa1, 0x1, 0x0, 0x44, 0x0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{HCI_GET, 0xa1, 0x1, 0x0, 0x44, 0x1}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{HCI_GET, 0xa8, 0x1, 0x0, 0x98, 0x0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{HCI_GET, 0xa8, 0x1, 0x0, 0x98, 0x1}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not all of these calls happen with the same frequency. The frequency also
changes when going between &lt;span class="caps"&gt;AC&lt;/span&gt; and&amp;nbsp;battery.&lt;/p&gt;
&lt;p&gt;The returned data makes no sense to me, but it does vary with system load, so
I suspect scaling and possibly masking is involved. However, I don&amp;rsquo;t have a good
way to go any further with this without going into questionable methods such as
decompilation. As such I have left this alone for&amp;nbsp;now.&lt;/p&gt;
&lt;h3 id="mysterious-other-calls"&gt;Mysterious other&amp;nbsp;calls&lt;/h3&gt;
&lt;p&gt;I don&amp;rsquo;t even know what these do, but I have observed them under&amp;nbsp;Windows:&lt;/p&gt;
&lt;p&gt;When locking the screen under&amp;nbsp;Windows: &lt;code&gt;{HCI_SET, 0x25, 0x2, 0x1, 0, 0}&lt;/code&gt;
When putting the system to sleep under&amp;nbsp;Windows: &lt;code&gt;{HCI_SET, 0xbd, 0x81, 0, 0, 0}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Linux currently only&amp;nbsp;uses &lt;code&gt;HCI_GET&lt;/code&gt; on &lt;code&gt;HCI_SYSTEM_INFO&lt;/code&gt;, Windows sometimes&amp;nbsp;uses
&lt;code&gt;HCI_SET&lt;/code&gt; too:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On screen&amp;nbsp;lock: &lt;code&gt;{HCI_SET, HCI_SYSTEM_INFO, 0, 1, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;On screen&amp;nbsp;unlock: &lt;code&gt;{HCI_SET, HCI_SYSTEM_INFO, 0, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Toshiba Service Station causes this call to be performed once when it is&amp;nbsp;opened:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{HCI_GET | 0x12, 0x9f, 0, 0, 0, 0}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;0x12&lt;/code&gt; makes no difference, but seems to be returned in the reply buffer.
Thus, I speculate that the lower byte can be used as a sort of &amp;ldquo;transaction &lt;span class="caps"&gt;ID&lt;/span&gt;&amp;rdquo;
to associate a request with a response. As to what the call does I can&amp;rsquo;t say,
but it returns the same value&amp;nbsp;(&lt;code&gt;0x5988&lt;/code&gt; in 4th integer in the buffer) every&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;In addition, on Windows, may calls that just fail (according to the status
codes) are performed.  These presumably are calls relevant to other&amp;nbsp;models.&lt;/p&gt;
&lt;h1 id="whats-next_2"&gt;What&amp;rsquo;s&amp;nbsp;next?&lt;/h1&gt;
&lt;p&gt;Now that everything is documented, and everything except the buttons tested,
what is the next&amp;nbsp;step?&lt;/p&gt;
&lt;p&gt;At this point is I plan to experiment changing the &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/platform/x86/toshiba_acpi.c?h=v5.19.2"&gt;toshiba_acpi&lt;/a&gt; kernel driver.
For implementing support for the &lt;a href="#hardware-buttons"&gt;hardware buttons&lt;/a&gt; I plan to
contact the relevant mailing list as I&amp;rsquo;m unsure how to proceed. Is a Toshiba
specific driver a good option or is a generic driver&amp;nbsp;better?&lt;/p&gt;</content><category term="Programming"></category><category term="Reverse Engineering"></category></entry><entry><title>Old school terminal: Informer D304</title><link href="https://vorpal.se/posts/2021/aug/12/old-school-terminal-informer-d304/" rel="alternate"></link><published>2021-08-12T00:00:00+02:00</published><updated>2021-08-12T00:00:00+02:00</updated><author><name>Arvid Norlander</name></author><id>tag:vorpal.se,2021-08-12:/posts/2021/aug/12/old-school-terminal-informer-d304/</id><summary type="html">&lt;p&gt;I found a weird looking terminal in a storage room / museum in the basement of the local&amp;nbsp;university:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Informer D304 Terminal" src="https://vorpal.se/images/retro_informer_d304/front.avif" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;A quick web search revealed that there was almost no information available of
this incredibly unergonomic terminal online, which peaked my curiosity. I was
able to find one image in a &lt;a href="http://vtda.org/docs/computing/Informer/Informer_401TerminalBrochure.pdf"&gt;scan …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I found a weird looking terminal in a storage room / museum in the basement of the local&amp;nbsp;university:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Informer D304 Terminal" src="https://vorpal.se/images/retro_informer_d304/front.avif" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;A quick web search revealed that there was almost no information available of
this incredibly unergonomic terminal online, which peaked my curiosity. I was
able to find one image in a &lt;a href="http://vtda.org/docs/computing/Informer/Informer_401TerminalBrochure.pdf"&gt;scan of a marketing brochure&lt;/a&gt;
as well as a mention (without photos) on &lt;a href="http://terminals-wiki.org/wiki/index.php/Informer_D304"&gt;a wiki&lt;/a&gt;
about&amp;nbsp;terminals.&lt;/p&gt;
&lt;h1 id="what-is-a-terminal"&gt;What is a&amp;nbsp;terminal?&lt;/h1&gt;
&lt;p&gt;The uninitiated might at this point wonder what this is. What do I mean by a
terminal? A terminal was not a computer, but would be used to connect to a
large central computer (think hundreds of kilos and very expensive).
Multiple such terminals could be connected to the same computer to allow
many users to use the computer at once. This is a relic from the time
before &lt;em&gt;personal&lt;/em&gt;&amp;nbsp;computing.&lt;/p&gt;
&lt;h1 id="specs-history"&gt;Specs &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt;&amp;nbsp;history&lt;/h1&gt;
&lt;p&gt;From the two sources above we can learn some information and specs. The most interesting are probably&amp;nbsp;these:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Datapoint&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Introduction date&lt;/td&gt;
&lt;td&gt;March 1979&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface&lt;/td&gt;
&lt;td&gt;&lt;span class="caps"&gt;RS&lt;/span&gt;-232C or 20 mA current loop (optional). Daisy chaining multiple terminals was supported as well.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Printer interface&lt;/td&gt;
&lt;td&gt;Optional support for a &amp;#8220;buffered printer port&amp;#8221;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Baud rate&lt;/td&gt;
&lt;td&gt;50 to 19200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Character Set&lt;/td&gt;
&lt;td&gt;Upper and lower case, a total of 128 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display Format&lt;/td&gt;
&lt;td&gt;Multiple, not just the usual 80x24. Selectable by the host (presumably using control codes)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The company that made the terminal (Informer, Inc) appears to have been based
on Los Angeles, &lt;span class="caps"&gt;CA&lt;/span&gt;, &lt;span class="caps"&gt;USA&lt;/span&gt;. I have not been able to find any additional information
about them apart from their street address in the &lt;span class="caps"&gt;PDF&lt;/span&gt;&amp;nbsp;above.&lt;/p&gt;
&lt;h1 id="photos"&gt;Photos&lt;/h1&gt;
&lt;p&gt;Because of the lack of information online I decided to take some photos of the
unit to provide some documentation of it from various&amp;nbsp;angles:&lt;/p&gt;
&lt;p&gt;From the top, right next to some retro&amp;nbsp;computers:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Informer D304 Terminal - Top" src="https://vorpal.se/images/retro_informer_d304/top.avif" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;From the back we can see where the connectors are&amp;nbsp;mounted:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Informer D304 Terminal - Back" src="https://vorpal.se/images/retro_informer_d304/back.avif" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;When we take a closer look at the connectors (below), we can see that this model
appears to be lacking the printer port option. The &lt;span class="caps"&gt;RS&lt;/span&gt;-232 connectors appear to be a
standard &lt;span class="caps"&gt;DB&lt;/span&gt;-25 connector, but I&amp;#8217;m not sure if the ability to daisy chain to a
&amp;#8220;next terminal&amp;#8221; is a standard. I believe this unit does &lt;em&gt;not&lt;/em&gt; have the current
loop&amp;nbsp;option.&lt;/p&gt;
&lt;p&gt;Also, the power connector is not standard (8 pins, four on each row). This is
unfortunate as I was not able to spot any power brick nearby. Finally there is
a composite video&amp;nbsp;out.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Informer D304 Terminal - Connectors" src="https://vorpal.se/images/retro_informer_d304/back_connectors.avif" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Looking at the bottom of the device there is some &lt;span class="caps"&gt;DIP&lt;/span&gt; switch configuration information
as well as a nameplate (&lt;a href="https://vorpal.se/images/retro_informer_d304/bottom.avif"&gt;link to larger version&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Informer D304 Terminal - Bottom" src="https://vorpal.se/images/retro_informer_d304/bottom.avif" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Interestingly the device is marked as D304-K. The K seems to refer to this being
the keyboard unit, as the monitor is marked D304-M (M for&amp;nbsp;Monitor?):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Informer D304 Terminal - Monitor Label" src="https://vorpal.se/images/retro_informer_d304/screen_label.avif" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;There is also a contrast knob on the monitor underside as well as a tilt-and-swivel&amp;nbsp;stand:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Informer D304 Terminal - Monitor Mount" src="https://vorpal.se/images/retro_informer_d304/contrast_below_screen.avif" width="100%"&gt;&lt;/p&gt;
&lt;p&gt;Finally, a closer look at the keyboard layout. As can be seen
(&lt;a href="https://vorpal.se/images/retro_informer_d304/keyboard_layout.avif"&gt;link to larger version&lt;/a&gt;),
this is a Swedish layout, but with some unusual keys. For example the Ü key is not
standard on modern Swedish layouts, nor is it used in the Swedish language. Another
difference is that Shift-4 is usually ¤ not&amp;nbsp;$.&lt;/p&gt;
&lt;p&gt;Apart from that there are several differences to modern keyboards in general. For
example, there appears to be separate Return and &lt;span class="caps"&gt;LF&lt;/span&gt; (Line Feed?) keys. And the key
cluster on the left is quite&amp;nbsp;interesting.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Informer D304 Terminal - Keyboard Layout" src="https://vorpal.se/images/retro_informer_d304/keyboard_layout.avif" width="100%"&gt;&lt;/p&gt;
&lt;h1 id="whats-next-for-this-terminal"&gt;What&amp;#8217;s next for this&amp;nbsp;terminal?&lt;/h1&gt;
&lt;p&gt;I would love to be able to get this up and running (connected to a modern Linux computer
perhaps), but without the power brick I fear there is little chance of that&amp;nbsp;happening.&lt;/p&gt;
&lt;p&gt;If you have any more information regarding this type of terminal (especially with regards
to the power connector), please get in touch. I&amp;#8217;d love to be able to get this up and
running at some point (assuming it is not broken). To contact me about matters regarding
old computers&amp;nbsp;use &lt;code&gt;retro at &amp;lt;the domain of this website&amp;gt;&lt;/code&gt;.&lt;/p&gt;</content><category term="Retro Computing"></category><category term="Retro Computing"></category><category term="Informer D304"></category></entry><entry><title>QuickMCL vs AMCL performance</title><link href="https://vorpal.se/posts/2019/apr/07/quickmcl-vs-amcl-performance/" rel="alternate"></link><published>2019-04-07T00:00:00+02:00</published><updated>2019-12-10T00:00:00+01:00</updated><author><name>Arvid Norlander</name></author><id>tag:vorpal.se,2019-04-07:/posts/2019/apr/07/quickmcl-vs-amcl-performance/</id><summary type="html">&lt;p&gt;This article compares the performance of &lt;a href="https://github.com/VorpalBlade/quickmcl"&gt;QuickMCL&lt;/a&gt; and &lt;a href="https://wiki.ros.org/amcl"&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt;&lt;/a&gt;
when it comes to computational resources. It does not compare them for speed or
quality of localisation, but these should be identical if a parameter set is
used that is supported by both programs (e.g. likelihood field&amp;nbsp;model).&lt;/p&gt;
&lt;h1 id="test-methodology"&gt;Test&amp;nbsp;methodology …&lt;/h1&gt;</summary><content type="html">&lt;p&gt;This article compares the performance of &lt;a href="https://github.com/VorpalBlade/quickmcl"&gt;QuickMCL&lt;/a&gt; and &lt;a href="https://wiki.ros.org/amcl"&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt;&lt;/a&gt;
when it comes to computational resources. It does not compare them for speed or
quality of localisation, but these should be identical if a parameter set is
used that is supported by both programs (e.g. likelihood field&amp;nbsp;model).&lt;/p&gt;
&lt;h1 id="test-methodology"&gt;Test&amp;nbsp;methodology&lt;/h1&gt;
&lt;p&gt;The tests were made with a fixed number of particles, effectively disabling &lt;span class="caps"&gt;KLD&lt;/span&gt;
sampling &lt;a href="#foxKld2003" id="ref-foxKld2003-1"&gt;(Fox, 2003)&lt;/a&gt;. This is needed to get any sort of useful results, since
otherwise the results will not be&amp;nbsp;reproducible.&lt;/p&gt;
&lt;p&gt;The test consisted of playing back a &lt;span class="caps"&gt;ROS&lt;/span&gt; bag file with a recorded scenario and
measuring the &lt;span class="caps"&gt;CPU&lt;/span&gt; usage &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; memory usage at the end. This was repeated 5 times per
parameter value tested and the average was&amp;nbsp;taken.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;CPU&lt;/span&gt; (Intel Core i7 i7‑8550U) was set to run in performance mode to minimise
effects of &lt;span class="caps"&gt;CPU&lt;/span&gt; frequency scaling and optimisations (-O3) were enabled for
&lt;span class="caps"&gt;GCC&lt;/span&gt;&amp;nbsp;5.4.0‑6ubuntu1~16.04.11.&lt;/p&gt;
&lt;p&gt;For memory usage &lt;a href="https://en.wikipedia.org/wiki/Unique_set_size"&gt;unique set size&lt;/a&gt; was measured, which is the amount of
memory would be freed should the program exit at that point. This gives a fairer
measurement than resident set size, virtual set size or similar measurements,
since it will ignore shared memory-mapped resources such as the C standard
library, but it will include unique libraries used by the&amp;nbsp;program.&lt;/p&gt;
&lt;p&gt;The tests were run with the bag file played back at an accelerated rate, after
verifying that this did not change the results as long as the &lt;span class="caps"&gt;CPU&lt;/span&gt; load was below
100%, which was carefully&amp;nbsp;monitored.&lt;/p&gt;
&lt;p&gt;The code to re-create these results is available on
&lt;a href="https://github.com/VorpalBlade/ros_mcl_performance_test"&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="cpu-performance"&gt;&lt;span class="caps"&gt;CPU&lt;/span&gt;&amp;nbsp;performance&lt;/h1&gt;
&lt;p&gt;First, let us look at &lt;span class="caps"&gt;CPU&lt;/span&gt;&amp;nbsp;performance&lt;/p&gt;
&lt;p&gt;&lt;img alt="CPU performance" src="https://vorpal.se/images/mcl_perf/amcl_vs_quickmcl_cpu.svg" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;As can be seen, &lt;span class="caps"&gt;AMCL&lt;/span&gt; has quadratic complexity! Turns out this is from a poor
algorithm choice in the resampler where it uses a linear array of the sum of
particle weights, resulting in a complexity&amp;nbsp;of &lt;span class="math"&gt;\(O(m\cdot n)\)&lt;/span&gt; where m is the
number of new particles and n is the number of old&amp;nbsp;particles.&lt;/p&gt;
&lt;p&gt;As a contrast, QuickMCL uses a binary tree for this, resulting in a complexity&amp;nbsp;of &lt;span class="math"&gt;\(O(m\cdot\log n)\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Also of interest is that the conversion&amp;nbsp;from &lt;code&gt;LaserScan&lt;/code&gt; to &lt;code&gt;PointCloud2&lt;/code&gt; is
essentially free, but it should be noted that there is some overhead in running
it as two programs. Thus it is only recommended to use external laser
processing if you need the point cloud for something else anyway, or if you want
to do more advanced processing, such as merging data from multiple&amp;nbsp;lasers.&lt;/p&gt;
&lt;p&gt;The overhead is even larger if we include the resource usage of both programs
(not shown in this&amp;nbsp;graph).&lt;/p&gt;
&lt;p&gt;Also of note is that switching from the&amp;nbsp;default &lt;code&gt;float&lt;/code&gt; to &lt;code&gt;double&lt;/code&gt; for pose
components is essentially free when it comes to &lt;span class="caps"&gt;CPU&lt;/span&gt;&amp;nbsp;usage.&lt;/p&gt;
&lt;h1 id="memory-usage"&gt;Memory&amp;nbsp;usage&lt;/h1&gt;
&lt;p&gt;&lt;img alt="Memory usage" src="https://vorpal.se/images/mcl_perf/amcl_vs_quickmcl_memory.svg" width="100%"/&gt;&lt;/p&gt;
&lt;p&gt;Here, both implementations at least scale linearly, but &lt;span class="caps"&gt;AMCL&lt;/span&gt; does use
significantly more memory, both for the baseline and per&amp;nbsp;particle.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt; does have some structure members that are no longer in use (or only used in
certain situations) as well as cases of inefficient memory layout resulting in
unnecessary padding, but it seems strange it would cause such a huge&amp;nbsp;difference.&lt;/p&gt;
&lt;p&gt;Thus it isn&amp;rsquo;t clear what &lt;span class="caps"&gt;AMCL&lt;/span&gt; is doing that is causing it as even with internal
laser processing, QuickMCL doesn&amp;rsquo;t even come close to&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Also of note is that switching from the&amp;nbsp;default &lt;code&gt;float&lt;/code&gt; to &lt;code&gt;double&lt;/code&gt; for pose
components is essentially free for memory usage as&amp;nbsp;well.&lt;/p&gt;
&lt;h1 id="conclusions"&gt;Conclusions&lt;/h1&gt;
&lt;p&gt;There are three main points to draw from this&amp;nbsp;study:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;QuickMCL handily beats &lt;span class="caps"&gt;AMCL&lt;/span&gt; when it comes to resource usage, both for &lt;span class="caps"&gt;CPU&lt;/span&gt; and
  memory. &lt;span class="caps"&gt;AMCL&lt;/span&gt; is, of course, better tested and has more features (beam model,
  extra odometry models,&amp;nbsp;etc).&lt;/li&gt;
&lt;li&gt;It might be worth making double precision the default for QuickMCL since it
  doesn&amp;rsquo;t appear that the difference between single and double precision is very
  large, at least on modern x86&amp;nbsp;CPUs.&lt;/li&gt;
&lt;li&gt;External laser processing only make sense if you need the point cloud for
  something else, or need to do more advanced processing (such as merging data
  from multiple lasers). Otherwise, you are better off using the internal laser&amp;nbsp;processing.&lt;/li&gt;
&lt;/ul&gt;&lt;section class="bibliography"&gt;&lt;hr/&gt;&lt;h1 id="bibliography"&gt;Bibliography&lt;/h1&gt;&lt;p id="foxKld2003"&gt;Dieter Fox.
Adapting the &lt;span class="bibtex-protected"&gt;&lt;span class="bibtex-protected"&gt;Sample Size&lt;/span&gt;&lt;/span&gt; in &lt;span class="bibtex-protected"&gt;&lt;span class="bibtex-protected"&gt;Particle Filters Through KLD&lt;/span&gt;&lt;/span&gt;-&lt;span class="bibtex-protected"&gt;&lt;span class="bibtex-protected"&gt;Sampling&lt;/span&gt;&lt;/span&gt;.
&lt;em&gt;The International Journal of Robotics Research&lt;/em&gt;, 22(12):985&amp;ndash;1003, 12 2003.
&lt;a href="https://doi.org/10.1177/0278364903022012001"&gt;doi:10.1177/0278364903022012001&lt;/a&gt;. &lt;a class="cite-backref" href="#ref-foxKld2003-1" title="Jump back to reference 1"&gt;&amp;larrhk;&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;</content><category term="Programming"></category><category term="QuickMCL"></category><category term="AMCL"></category><category term="Localisation"></category><category term="ROS"></category></entry><entry><title>AMCL reverse engineering</title><link href="https://vorpal.se/posts/2019/apr/04/amcl-reverse-engineering/" rel="alternate"></link><published>2019-04-04T00:00:00+02:00</published><updated>2019-12-10T00:00:00+01:00</updated><author><name>Arvid Norlander</name></author><id>tag:vorpal.se,2019-04-04:/posts/2019/apr/04/amcl-reverse-engineering/</id><summary type="html">&lt;p&gt;This is my notes from reverse engineering &lt;a href="https://wiki.ros.org/amcl"&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt;&lt;/a&gt;. I
did this to get a better understanding of the state of the art localisation when
implementing my own Monte Carlo Localisation as a course works project during my
masters in robotics&amp;nbsp;degree.&lt;/p&gt;
&lt;p&gt;Hopefully, it will be useful to someone else, but …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is my notes from reverse engineering &lt;a href="https://wiki.ros.org/amcl"&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt;&lt;/a&gt;. I
did this to get a better understanding of the state of the art localisation when
implementing my own Monte Carlo Localisation as a course works project during my
masters in robotics&amp;nbsp;degree.&lt;/p&gt;
&lt;p&gt;Hopefully, it will be useful to someone else, but remember that you learn by
doing it yourself than by just reading about&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;Note that this is an analysis of what &lt;span class="caps"&gt;AMCL&lt;/span&gt; does differently than the book
[@thrunProbabilisticRobotics2005], and thus it assumes the reader understands
the basic&amp;nbsp;concept.&lt;/p&gt;
&lt;h1 id="odometry"&gt;Odometry&lt;/h1&gt;
&lt;p&gt;There are two interesting extras&amp;nbsp;here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(\delta_{trans}\)&lt;/span&gt; is too small (&amp;lt;0.01 m),&amp;nbsp;set &lt;span class="math"&gt;\(\delta_{rot1}\)&lt;/span&gt; to 0. This
   avoids some numerical instability when rotating in&amp;nbsp;place.&lt;/li&gt;
&lt;li&gt;It has a rather neat check for driving backwards, taking the minimum&amp;nbsp;of
   &lt;span class="math"&gt;\(\delta_{rotN}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\pi-\delta_{rotN}\)&lt;/span&gt; as the basis for the variances. This
   prevents severe overestimating of the noise when&amp;nbsp;reversing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both of these features have been&amp;nbsp;implemented.&lt;/p&gt;
&lt;h1 id="map-from-likelihood-model-perspective"&gt;Map (from likelihood model&amp;nbsp;perspective)&lt;/h1&gt;
&lt;p&gt;For the map &lt;span class="caps"&gt;AMCL&lt;/span&gt; stores two attributes per&amp;nbsp;cell:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Occupancy state (occupied, free,&amp;nbsp;unknown)&lt;/li&gt;
&lt;li&gt;Distance to the nearest occupied&amp;nbsp;cell.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means &lt;span class="caps"&gt;AMCL&lt;/span&gt; does not use the full probability information in the original
evidence&amp;nbsp;grid.&lt;/p&gt;
&lt;p&gt;The distance to nearest occupied cell is computed via a something similar to
Dijkstra&amp;rsquo;s algorithm, flood filling outwards from the occupied cells, up to a
limited max distance. The distance computed appears to be the L2 norm&amp;nbsp;however.&lt;/p&gt;
&lt;p&gt;Side note: This is computed in a really smart way, pre-computing a lookup table
for one quadrant of the grid (up to the limited max distance). This can be found&amp;nbsp;in &lt;code&gt;CachedDistanceMap&lt;/code&gt; in &lt;code&gt;map_cspace.cpp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Side side note: However, it could be optimised further since currently it uses
double-pointer indirection&amp;nbsp;(&lt;code&gt;double**&lt;/code&gt;) as opposed to linearising the array and
doing it with a multiplication followed by addition. This is a
micro-optimisation&amp;nbsp;however.&lt;/p&gt;
&lt;h1 id="sensor-model-likelihood-field"&gt;Sensor model: Likelihood&amp;nbsp;field&lt;/h1&gt;
&lt;p&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt; has both likelihood models (two of them) and a beam model, but the focus
for this analysis is exclusively on the likelihood&amp;nbsp;model.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt; uses a limited number of beams (30 by default), spaced equidistantly in the
sensor&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Because of the difference in the map it is non-trivial to work out if I&amp;rsquo;m doing
the same thing as &lt;span class="caps"&gt;AMCL&lt;/span&gt; for computing the probability, but here is what &lt;span class="caps"&gt;AMCL&lt;/span&gt; does
in Python-esque pseudo code&amp;nbsp;(&amp;ldquo;de-optimised&amp;rdquo;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;particle&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cloud&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;particle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;laser_offset&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;beam&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;subset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_beams&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;sanity_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# check beam for max range, NaN etc&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trigonometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;map_coords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;map_coords&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;outside_map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_occ_dist&lt;/span&gt;  &lt;span class="c1"&gt;# Max distance we precomputed distances for&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;map_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;map_coords&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;occ_dist&lt;/span&gt;
        &lt;span class="n"&gt;pz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z_hit&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sigma_hit&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;pz&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;z_rand&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;range_max&lt;/span&gt;
        &lt;span class="c1"&gt;# Here is a helpful assert to ensure pz is in [0, 1]&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;pz&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="n"&gt;particle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;All the time, the total weight is tracked as well and everything is normalised
to [0, 1] at the end (assuming non-zero&amp;nbsp;weights).&lt;/p&gt;
&lt;p&gt;From this, it can be seen that &lt;span class="caps"&gt;AMCL&lt;/span&gt; does not use the algorithm in the book, but
rather adds probabilities, but in a non-linear way (cubing them). The cubing is
documented as &amp;ldquo;ad-hoc&amp;rdquo; but &amp;ldquo;works well&amp;rdquo;. In the range of interest [0, 1] this
has the effect of reducing the values. However, this reduction is non-linear and
small values will be reduced&amp;nbsp;more.&lt;/p&gt;
&lt;p&gt;In addition to this algorithm, there is an extended likelihood field algorithm
with something called beam skipping (not enabled by default), which according to
the documentation is supposed to help with unexpected obstacles. If enabled,
this part of the algorithm is still only active after the particle set has&amp;nbsp;converged.&lt;/p&gt;
&lt;p&gt;In a strange oversight, &lt;span class="caps"&gt;AMCL&lt;/span&gt; does not precompute the probability, only the
distance to the nearest obstacles. It would have been quite possible to
precompute the probability on that map. Maybe this is because &lt;span class="caps"&gt;AMCL&lt;/span&gt; has some sort
of support for dynamic parameter change, which would break this&amp;nbsp;if &lt;code&gt;sigma_hit&lt;/code&gt;
would change without getting a new&amp;nbsp;map?&lt;/p&gt;
&lt;h1 id="particle-filter"&gt;Particle&amp;nbsp;filter&lt;/h1&gt;
&lt;p&gt;As for the particle filter itself, its design is informed by the choice
of resampling procedure (&lt;span class="caps"&gt;KLD&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;The filter has a min and max number of particles, as well as various particles
related to &lt;span class="caps"&gt;KLD&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="convergence"&gt;Convergence&lt;/h2&gt;
&lt;p&gt;There is the concept of convergence of the filter. This is computed by looking
at if all particles are within a certain threshold of the mean position. Only
x and y are considered for&amp;nbsp;this, &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; is not used. This seems to be used
exclusively for the likelihood field with beam skipping&amp;nbsp;model.&lt;/p&gt;
&lt;h2 id="action-sensor-update"&gt;Action &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; sensor&amp;nbsp;update&lt;/h2&gt;
&lt;p&gt;The action update is straight-forward, trivial even, just processing all the
particles with the odometry model in&amp;nbsp;use.&lt;/p&gt;
&lt;p&gt;The sensor update has a couple of extra steps however after the sensor model
has executed. Simplified&amp;nbsp;pseudo-python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_weight&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;normalise_weights&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;w_slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;low_pass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w_slow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_weight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha_slow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w_fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;low_pass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w_fast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;avg_weight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha_fast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;set_all_weights&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;particle_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As a side note, in this code there is both a very stupid thing and a very smart&amp;nbsp;thing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It recomputes the total to compute avg_weight, even though it already has
  the total&amp;nbsp;(stupid).&lt;/li&gt;
&lt;li&gt;The low pass filter is implemented&amp;nbsp;as &lt;code&gt;x = x + alpha * (x' - x)&lt;/code&gt; instead of
  the more common&amp;nbsp;expression &lt;code&gt;x = (1-alpha) * x + alpha * y&lt;/code&gt;. This is one less
  arithmetic operation&amp;nbsp;(smart).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="resampling"&gt;Resampling&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt; uses adaptive &lt;span class="caps"&gt;KLD&lt;/span&gt; resampling. According to comments, this is incompatible
with low variance sampling, so that isn&amp;rsquo;t&amp;nbsp;used.&lt;/p&gt;
&lt;p&gt;By default, the adaptive sampling is not enabled (both alpha parameters are set
to 0), but enabling it does seem to improve&amp;nbsp;localisation.&lt;/p&gt;
&lt;p&gt;Pseudo code for what it&amp;nbsp;does:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Switch between two particle sets&lt;/span&gt;
    &lt;span class="n"&gt;set_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_set&lt;/span&gt;
    &lt;span class="n"&gt;set_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next_set&lt;/span&gt;
    &lt;span class="c1"&gt;# Cumulative probability table (huh?)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;double&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set_a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;running_total&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;set_a&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="c1"&gt;# KD-tree for adaptive sampling (huh?)&lt;/span&gt;
    &lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kdtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;w_diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w_fast&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w_slow&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w_diff&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;w_diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_samples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sample_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;uniform_rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;w_diff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# See amcl_node.cpp uniformPoseGenerator, basically generates random&lt;/span&gt;
            &lt;span class="c1"&gt;# poses inside known free spaces&lt;/span&gt;
            &lt;span class="n"&gt;sample_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generate_random_pose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# "Naive discrete event sampler"&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uniform_rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;set_a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;sample_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;set_a&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;sample_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;  &lt;span class="c1"&gt;# Surely this will be overwritten soon anyway?&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;sample_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;
        &lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kdtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# KLD sample check:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resample_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kdtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leaf_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="c1"&gt;# Reset low pass filters, to not make it too random&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w_diff&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w_slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w_fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;normalise_weights&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Compute cluster statistics&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cluster_statistics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;swap_particle_sets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Only used for beamskip model&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_convergence&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;span class="caps"&gt;KLD&lt;/span&gt; criteria seem to be slightly different than what the book has, need to
look further into the &lt;span class="caps"&gt;KD&lt;/span&gt;-tree implementation used&amp;nbsp;here.&lt;/p&gt;
&lt;p&gt;The function responsible for the &lt;span class="caps"&gt;KLD&lt;/span&gt; criterion has the following&amp;nbsp;pseudo-code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resample_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Apparently taken directly from "Fox et al."&lt;/span&gt;
    &lt;span class="c1"&gt;# Appears to be code on page 264, table 8.4, lines 15-16&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_samples&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop_z&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop_err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Limit to [min, max]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_samples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_samples&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_samples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_samples&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;span class="caps"&gt;KD&lt;/span&gt;-tree is interesting in that it inserts into a discrete grid, tracking the
total weight for each cell (used for cluster handling later&amp;nbsp;on).&lt;/p&gt;
&lt;p&gt;The grid size is 0.5 meters in x/y and 10 degrees&amp;nbsp;in &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; (hardcoded).&lt;/p&gt;
&lt;h2 id="clusters"&gt;Clusters&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt; identifies clusters in the sample sets, this appears to be used for the
actual localisation to select the best candidate as well as compute the
covariance of the&amp;nbsp;filter.&lt;/p&gt;
&lt;p&gt;The clustering is quite simple, using the coarse &lt;span class="caps"&gt;KD&lt;/span&gt;-tree from the &lt;span class="caps"&gt;KLD&lt;/span&gt;&amp;nbsp;buckets.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Put all leaves in a&amp;nbsp;queue.&lt;/li&gt;
&lt;li&gt;For each item in the&amp;nbsp;queue:&lt;/li&gt;
&lt;li&gt;Check if it is already in a cluster, if so skip to the next&amp;nbsp;node&lt;/li&gt;
&lt;li&gt;Assign a cluster &lt;span class="caps"&gt;ID&lt;/span&gt; to the node (using a&amp;nbsp;counter).&lt;/li&gt;
&lt;li&gt;Check all neighbour buckets&amp;nbsp;(&lt;span class="math"&gt;\(3^3\)&lt;/span&gt;) to see if they are non-empty, and if
      so assign them to the same cluster. Do this recursively. There is a slight
      bug here however, in that the algorithm doesn&amp;rsquo;t handle warparound for the
      neighbours&amp;nbsp;in &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then the code processes the&amp;nbsp;clusters:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Reset the statistics (mean, covariance, summed weight, particle count) for
   the&amp;nbsp;clusters&lt;/li&gt;
&lt;li&gt;Reset overall filter statistics (as&amp;nbsp;above)&lt;/li&gt;
&lt;li&gt;For each&amp;nbsp;particle:&lt;/li&gt;
&lt;li&gt;Get the cluster &lt;span class="caps"&gt;ID&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Update statistics for both the total filter and each&amp;nbsp;cluster&lt;/li&gt;
&lt;li&gt;For each cluster normalise the statistics: Divide by number of particles to
   get mean, compute final covariance and so&amp;nbsp;on.&lt;/li&gt;
&lt;li&gt;Same for overall filter&amp;nbsp;statistics&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Later on, it will use the cluster with the highest total&amp;nbsp;weight.&lt;/p&gt;
&lt;h1 id="overall-node_1"&gt;Overall&amp;nbsp;node&lt;/h1&gt;
&lt;p&gt;For the &lt;span class="caps"&gt;ROS&lt;/span&gt; node itself, there appears to be multi-threading going on,
since there is a mutex being used. However, this seems to only be protecting
the configuration parameters. May be related to the support for dynamic
reconfiguration. However, some other &lt;span class="caps"&gt;ROS&lt;/span&gt; packages do not use mutexes to protect
dynamic&amp;nbsp;reconfiguration.&lt;/p&gt;
&lt;h2 id="the-laserreceived-callback"&gt;The laserReceived&amp;nbsp;callback&lt;/h2&gt;
&lt;p&gt;The function of most interest would be laserReceived, where the actual
localisation code is&amp;nbsp;executed.&lt;/p&gt;
&lt;p&gt;There appears to be some sort of support for having multiple lasers as well as
caching of transforms related to&amp;nbsp;this.&lt;/p&gt;
&lt;p&gt;There is a&amp;nbsp;flag, &lt;code&gt;pf_init&lt;/code&gt; to special-case the first&amp;nbsp;iteration&lt;/p&gt;
&lt;p&gt;A very high level overview of the function is (with multi-laser code&amp;nbsp;removed):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;laser_received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;has_map&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;get_laser_transform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_odometry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;force_publication&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pf_init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;force_publication&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="n"&gt;pose_delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pose&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prev_pose&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_filter_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pose_delta&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pf_init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prev_pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pose&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pf_init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_filter_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resample_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pf_init&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_filter_update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;run_odometry_model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;resampled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_filter_update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;run_sensor__model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_filter_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prev_pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pose&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resample_count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resample_interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;resample&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;resampled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="n"&gt;publish_cloud&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resampled&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;force_publication&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Compute single position hypothesis&lt;/span&gt;
        &lt;span class="n"&gt;max_weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;clusters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;best_cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;
                &lt;span class="n"&gt;max_weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;
        &lt;span class="n"&gt;estimated_pose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best_cluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;covariance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;publish_pose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimated_pose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;publish_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estimated_pose&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;odom_transform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;republish_last_transform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From this analysis it appears there is nothing particularly unexpected, but the
code is complicated quite a bit by handling multiple lasers, lasers mounted
upside down, and many other&amp;nbsp;things.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt; does resampling unconditionally&amp;nbsp;every &lt;code&gt;resample_interval&lt;/code&gt; filter updates,
by default&amp;nbsp;2.&lt;/p&gt;
&lt;h1 id="amcl-bugs_1"&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt;&amp;nbsp;bugs&lt;/h1&gt;
&lt;p&gt;I found some bugs in &lt;span class="caps"&gt;AMCL&lt;/span&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clustering doesn&amp;rsquo;t handle wrap-around&amp;nbsp;from &lt;span class="math"&gt;\(-\pi\)&lt;/span&gt; to &lt;span class="math"&gt;\(\pi\)&lt;/span&gt; (or vice versa).
   (&lt;a href="https://github.com/ros-planning/navigation/issues/27"&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt; bug&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Clustering is broken for cluster &lt;span class="caps"&gt;ID&lt;/span&gt; 0, since it uses 0 both for &amp;ldquo;not yet
   assigned&amp;rdquo; and &amp;ldquo;first cluster&amp;rdquo;. It should, however, work out in the end since
   that first cluster will just be written to twice, getting &lt;span class="caps"&gt;ID&lt;/span&gt; 1 (assuming it
   consists of more than one&amp;nbsp;bucket).&lt;/li&gt;
&lt;li&gt;Incorrect computation of circular variance, it is actually computing circular
   standard deviation.
   (&lt;a href="https://github.com/ros-planning/navigation/issues/869"&gt;&lt;span class="caps"&gt;AMCL&lt;/span&gt; bug&lt;/a&gt;)&lt;br/&gt;
&lt;strong&gt;Update&lt;/strong&gt; (2019-04-14): Turns out this was not actually a bug. The two
   expressions are in fact&amp;nbsp;equivalent.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;There is a lot of smart code in &lt;span class="caps"&gt;AMCL&lt;/span&gt;, but also quite a lot of stupid code, and
it is all pretty poorly documented. Parts are written in C and parts in C++.
Not unexpected of code with its roots in research that many people have worked
on over the&amp;nbsp;years.&lt;/p&gt;
&lt;p&gt;I also found some &lt;a href="#AMCL_bugs_1"&gt;bugs&lt;/a&gt;, mostly with minor impacts. In addition
to the bugs, several places use sub-optimal algorithms (as demonstrated above)
or compute the same value more than once&amp;nbsp;unnecessarily.&lt;/p&gt;
&lt;p&gt;Oh and by the way, I put up my own &lt;span class="caps"&gt;MCL&lt;/span&gt; implementation publicly, called
&lt;a href="https://github.com/VorpalBlade/quickmcl"&gt;QuickMCL&lt;/a&gt;. It lacks some features that
&lt;span class="caps"&gt;AMCL&lt;/span&gt; has but uses less computational&amp;nbsp;resources.&lt;/p&gt;</content><category term="Programming"></category><category term="AMCL"></category><category term="Localisation"></category><category term="ROS"></category></entry></feed>