<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Leif&#39;s Website</title>
    <link>https://leifmetcalf.com/</link>
    <description></description>
    <pubDate>Thu, 16 Apr 2026 16:47:36 +0000</pubDate>
    <image>
      <url>https://i.snap.as/srNhIciY.png</url>
      <title>Leif&#39;s Website</title>
      <link>https://leifmetcalf.com/</link>
    </image>
    <item>
      <title>Walks in Sydney</title>
      <link>https://leifmetcalf.com/walks-in-sydney?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[This blog has many walks around Sydney: https://hikingtheworld.blog/hiking-guides/overnight-walks-near-sydney/&#xA;&#xA;!--more--&#xA;&#xA;Mount Solitary Loop&#xA;&#xA;Overnight. Starts and ends at Katoomba.&#xA;&#xA;Day 1: Descend into the valley, follow Federal Pass west, then Ruined Castle Walking Track. Camp at Chinaman&#39;s Gully or elsewhere on the plateau.&#xA;&#xA;Day 2: Follow the Mount Solitary ridge line down to Sublime Point Trail, and walk back to Federal Pass. Depending on where you camp this last day can be quite long, so leave early.&#xA;&#xA;The only reliable source of water is at Kedumba River on the second day, so bring plenty of water.&#xA;&#xA;Splendour Rock via Narrow Neck&#xA;&#xA;Overnight. Starts and ends at Katoomba.&#xA;&#xA;Day 1: Cycle down Narrow Neck, dump bikes at the lookout. Take Taros Ladder or Duncans Pass down and follow the track to the walk&#39;s best water source, at Mobbs Soak.&#xA;&#xA;Take the path left of the toilet and continue until you see a cairn marking a left turn onto the trail up to Mount Dingo. Camp anywhere before Splendour Rock.&#xA;&#xA;Day 2: The reverse.&#xA;&#xA;There is apparently a place upstream at Mobbs to get water, but we couldn&#39;t find it, so we got water here from a small drip below the boardwalks before the campsite.&#xA;&#xA;The trail near Mobbs Soak was very leechy in late March.&#xA;&#xA;There&#39;s an alternate route that goes up over the ridge line, instead of alongside it, starting from the end of the fire trail, but we were advised against it by some other trampers. Apparently it&#39;s quite overgrown. On the second day we checked out the trail at the Mount Dingo side, and it seemed ok there.]]&gt;</description>
      <content:encoded><![CDATA[<p>This blog has many walks around Sydney: <a href="https://hikingtheworld.blog/hiking-guides/overnight-walks-near-sydney/">https://hikingtheworld.blog/hiking-guides/overnight-walks-near-sydney/</a></p>

<p><img src="https://i.snap.as/jg3gKwUO.png" alt=""/></p>



<h2 id="mount-solitary-loop" id="mount-solitary-loop">Mount Solitary Loop</h2>

<p>Overnight. Starts and ends at Katoomba.</p>

<p>Day 1: Descend into the valley, follow Federal Pass west, then Ruined Castle Walking Track. Camp at Chinaman&#39;s Gully or elsewhere on the plateau.</p>

<p>Day 2: Follow the Mount Solitary ridge line down to Sublime Point Trail, and walk back to Federal Pass. Depending on where you camp this last day can be quite long, so leave early.</p>

<p>The only reliable source of water is at Kedumba River on the second day, so bring plenty of water.</p>

<h2 id="splendour-rock-via-narrow-neck" id="splendour-rock-via-narrow-neck">Splendour Rock via Narrow Neck</h2>

<p>Overnight. Starts and ends at Katoomba.</p>

<p>Day 1: Cycle down Narrow Neck, dump bikes at the lookout. Take Taros Ladder or Duncans Pass down and follow the track to the walk&#39;s best water source, at Mobbs Soak.</p>

<p>Take the path left of the toilet and continue until you see a cairn marking a left turn onto the trail up to Mount Dingo. Camp anywhere before Splendour Rock.</p>

<p>Day 2: The reverse.</p>

<p>There is apparently a place upstream at Mobbs to get water, but we couldn&#39;t find it, so we got water here from a small drip below the boardwalks before the campsite.</p>

<p>The trail near Mobbs Soak was very leechy in late March.</p>

<p>There&#39;s an alternate route that goes up over the ridge line, instead of alongside it, starting from the end of the fire trail, but we were advised against it by some other trampers. Apparently it&#39;s quite overgrown. On the second day we checked out the trail at the Mount Dingo side, and it seemed ok there.</p>
]]></content:encoded>
      <guid>https://leifmetcalf.com/walks-in-sydney</guid>
      <pubDate>Wed, 25 Mar 2026 10:32:53 +0000</pubDate>
    </item>
    <item>
      <title>Advent of Code 2024 Day 24</title>
      <link>https://leifmetcalf.com/advent-of-code-2024-day-24?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[The interesting function is repair, which compares the expression for e.g. z11 with the objective adder expression for z11. If we can&#39;t find a node with the objective expression, we recurse into the sub-expressions and try repair those.&#xA;!--more--&#xA;import sys&#xA;from bidict import bidict&#xA;&#xA;gates = {}&#xA;nodes = set()&#xA;&#xA;for line in sys.stdin.read().split(&#39;\n\n&#39;)[1].splitlines():&#xA;    a, op, b, , c = line.split()&#xA;    gates[c] = (op, a, b)&#xA;    nodes.update((a, b, c))&#xA;&#xA;nodes = sorted(nodes)&#xA;x = [p for p in nodes if p[0] == &#39;x&#39;]&#xA;y = [p for p in nodes if p[0] == &#39;y&#39;]&#xA;z = [p for p in nodes if p[0] == &#39;z&#39;]&#xA;&#xA;def generateexprs(gates):&#xA;    exprs = bidict()&#xA;    def go(p):&#xA;        if p in exprs:&#xA;            return&#xA;        if p in x or p in y:&#xA;            exprs[p] = (p,)&#xA;        else:&#xA;            op, a, b = gates[p]&#xA;            go(a)&#xA;            go(b)&#xA;            exprs[p] = (op, *sorted((exprs[a], exprs[b])))&#xA;    for p in nodes:&#xA;        go(p)&#xA;    return exprs&#xA;&#xA;def objective(n):&#xA;    if n == 0:&#xA;        return (&#39;XOR&#39;, (&#39;x00&#39;,), (&#39;y00&#39;,))&#xA;    else:&#xA;        return (&#39;XOR&#39;, carry(n - 1), (&#39;XOR&#39;, (x[n],), (y[n],)))&#xA;&#xA;def carry(n):&#xA;    if n == 0:&#xA;        return (&#39;AND&#39;, (&#39;x00&#39;,), (&#39;y00&#39;,))&#xA;    else:&#xA;        return (&#39;OR&#39;, (&#39;AND&#39;, carry(n - 1), (&#39;XOR&#39;, (x[n],), (y[n],))), (&#39;AND&#39;, (x[n],), (y[n],)))&#xA;&#xA;def repair(gates, exprs, p, obj):&#xA;    def go(p, obj):&#xA;        if exprs[p] == obj:&#xA;            return&#xA;        if (res := exprs.inv.get(obj)):&#xA;            return (p, res)&#xA;        else:&#xA;            op, a, b = gates[p]&#xA;            objop, obja, objb = obj&#xA;            if op != objop:&#xA;                raise Exception&#xA;            if go(a, obja) is None:&#xA;                return go(b, objb)&#xA;            if go(b, objb) is None:&#xA;                return go(a, obja)&#xA;            if go(a, objb) is None:&#xA;                return go(b, obja)&#xA;            if go(b, obja) is None:&#xA;                return go(a, objb)&#xA;            raise Exception&#xA;    return go(p, obj)&#xA;&#xA;swaps = []&#xA;&#xA;for i in range(45):&#xA;    exprs = generate_exprs(gates)&#xA;    match repair(gates, exprs, z[i], objective(i)):&#xA;        case (p, q):&#xA;            swaps.extend((p, q))&#xA;            gates[p], gates[q] = gates[q], gates[p]&#xA;&#xA;print(&#39;,&#39;.join(sorted(swaps)))&#xA;`]]&gt;</description>
      <content:encoded><![CDATA[<p>The interesting function is <code>repair</code>, which compares the expression for e.g. <code>z11</code> with the objective adder expression for <code>z11</code>. If we can&#39;t find a node with the objective expression, we recurse into the sub-expressions and try repair those.
</p>

<pre><code class="language-python">import sys
from bidict import bidict

gates = {}
nodes = set()

for line in sys.stdin.read().split(&#39;\n\n&#39;)[1].splitlines():
    a, op, b, _, c = line.split()
    gates[c] = (op, a, b)
    nodes.update((a, b, c))

nodes = sorted(nodes)
x = [p for p in nodes if p[0] == &#39;x&#39;]
y = [p for p in nodes if p[0] == &#39;y&#39;]
z = [p for p in nodes if p[0] == &#39;z&#39;]

def generate_exprs(gates):
    exprs = bidict()
    def go(p):
        if p in exprs:
            return
        if p in x or p in y:
            exprs[p] = (p,)
        else:
            op, a, b = gates[p]
            go(a)
            go(b)
            exprs[p] = (op, *sorted((exprs[a], exprs[b])))
    for p in nodes:
        go(p)
    return exprs

def objective(n):
    if n == 0:
        return (&#39;XOR&#39;, (&#39;x00&#39;,), (&#39;y00&#39;,))
    else:
        return (&#39;XOR&#39;, carry(n - 1), (&#39;XOR&#39;, (x[n],), (y[n],)))

def carry(n):
    if n == 0:
        return (&#39;AND&#39;, (&#39;x00&#39;,), (&#39;y00&#39;,))
    else:
        return (&#39;OR&#39;, (&#39;AND&#39;, carry(n - 1), (&#39;XOR&#39;, (x[n],), (y[n],))), (&#39;AND&#39;, (x[n],), (y[n],)))

def repair(gates, exprs, p, obj):
    def go(p, obj):
        if exprs[p] == obj:
            return
        if (res := exprs.inv.get(obj)):
            return (p, res)
        else:
            op, a, b = gates[p]
            obj_op, obj_a, obj_b = obj
            if op != obj_op:
                raise Exception
            if go(a, obj_a) is None:
                return go(b, obj_b)
            if go(b, obj_b) is None:
                return go(a, obj_a)
            if go(a, obj_b) is None:
                return go(b, obj_a)
            if go(b, obj_a) is None:
                return go(a, obj_b)
            raise Exception
    return go(p, obj)

swaps = []

for i in range(45):
    exprs = generate_exprs(gates)
    match repair(gates, exprs, z[i], objective(i)):
        case (p, q):
            swaps.extend((p, q))
            gates[p], gates[q] = gates[q], gates[p]

print(&#39;,&#39;.join(sorted(swaps)))
</code></pre>
]]></content:encoded>
      <guid>https://leifmetcalf.com/advent-of-code-2024-day-24</guid>
      <pubDate>Wed, 10 Dec 2025 01:34:05 +0000</pubDate>
    </item>
    <item>
      <title>Ecto hierarchical queries beyond preload</title>
      <link>https://leifmetcalf.com/ecto-hierarchical-queries?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[The usual way to get a tree of nested structs from an Ecto query is with preload. The preload option lets you load associations for an Ecto schema, either by issuing a separate query:&#xA;&#xA;!--more--&#xA;&#xA;Repo.all from p in Post, preload: [:comments]&#xA;or by restructuring the result of an explicitly written join tree:&#xA;Repo.all from p in Post,&#xA;  join: c in assoc(p, :comments),&#xA;  where: c.publishedat   p.updatedat,&#xA;  preload: [comments: c]&#xA;This works when the nested structs are already associations in the schema, but what if we want to load ad-hoc fields, or if we don&#39;t want to use a schema at all?&#xA;&#xA;Manually rearranging the result in Elixir&#xA;&#xA;For example, suppose users can react to comments, so each Comment has a number of Reaction children. How would we return a list of all posts, as well as the most recent comment for each post, as well as all the reactions for each comment? I.e. we want to somehow return:&#xA;[&#xA;  %{&#xA;    post: %Post{},&#xA;    lastcomment: %Comment{&#xA;      reactions: [&#xA;        %Reaction{},&#xA;        ...&#xA;      ]&#xA;    }&#xA;  },&#xA;  ...&#xA;]&#xA;The SQL is straightforward:&#xA;select ...&#xA;from posts p&#xA;left lateral join (&#xA;  select ...&#xA;  from comments c&#xA;  where c.postid = p.id&#xA;  order by c.insertedat desc&#xA;  limit 1&#xA;) lc on true&#xA;left join reactions r on r.commentid = lc.id&#xA;One option is to translate the SQL directly into Ecto query syntax:&#xA;Repo.all from p in Posts,&#xA;  as: :post,&#xA;  leftlateraljoin: lc in subquery(&#xA;    from c in Comment,&#xA;    where: c.postid == parentas(:post).id,&#xA;    orderby: [:desc, c.insertedat],&#xA;    limit: 1),&#xA;  on: true,&#xA;  leftjoin: reaction in assoc(lc, :reactions),&#xA;  select: %{post: p, lastcomment: lc, reaction: r}&#xA;)&#xA;This returns a flat list of rows, so we need to manually restructure the result into a list of nested structs:&#xA;Input:&#xA;[&#xA;%{post: %Post{}, lastcomment: %Comment{}, reaction: %Reaction{}},&#xA;...&#xA;]&#xA;&#xA;Output:&#xA;[&#xA;%{post: %Post{}, lastcomment: %Comment{reactions: [%Reaction{}, ...]}}&#xA;]&#xA;&#xA;def restructure(rows) do&#xA;  # Elixir code goes here&#xA;end&#xA;This is a fair bit of work, but workable and very flexible.&#xA;&#xA;Using JSON&#xA;&#xA;We can save some work by returning a tree-like object from the database with json_agg:&#xA;&#xA;Add association and preload&#xA;&#xA;Cannot be done dynamically. Why?&#xA;&#xA;Return json and cast]]&gt;</description>
      <content:encoded><![CDATA[<p>The usual way to get a tree of nested structs from an Ecto query is with <code>preload</code>. The <code>preload</code> option lets you load associations for an Ecto schema, either by issuing a separate query:</p>



<pre><code class="language-elixir">Repo.all from p in Post, preload: [:comments]
</code></pre>

<p>or by restructuring the result of an explicitly written join tree:</p>

<pre><code class="language-elixir">Repo.all from p in Post,
  join: c in assoc(p, :comments),
  where: c.published_at &gt; p.updated_at,
  preload: [comments: c]
</code></pre>

<p>This works when the nested structs are already associations in the schema, but what if we want to load ad-hoc fields, or if we don&#39;t want to use a schema at all?</p>

<h2 id="manually-rearranging-the-result-in-elixir" id="manually-rearranging-the-result-in-elixir">Manually rearranging the result in Elixir</h2>

<p>For example, suppose users can react to comments, so each <code>Comment</code> has a number of <code>Reaction</code> children. How would we return a list of all posts, as well as the most recent comment for each post, as well as all the reactions for each comment? I.e. we want to somehow return:</p>

<pre><code class="language-elixir">[
  %{
    post: %Post{},
    last_comment: %Comment{
      reactions: [
        %Reaction{},
        ...
      ]
    }
  },
  ...
]
</code></pre>

<p>The SQL is straightforward:</p>

<pre><code class="language-sql">select ...
from posts p
left lateral join (
  select ...
  from comments c
  where c.post_id = p.id
  order by c.inserted_at desc
  limit 1
) lc on true
left join reactions r on r.comment_id = lc.id
</code></pre>

<p>One option is to translate the SQL directly into Ecto query syntax:</p>

<pre><code class="language-elixir">Repo.all from p in Posts,
  as: :post,
  left_lateral_join: lc in subquery(
    from c in Comment,
    where: c.post_id == parent_as(:post).id,
    order_by: [:desc, c.inserted_at],
    limit: 1),
  on: true,
  left_join: reaction in assoc(lc, :reactions),
  select: %{post: p, last_comment: lc, reaction: r}
)
</code></pre>

<p>This returns a flat list of rows, so we need to manually restructure the result into a list of nested structs:</p>

<pre><code class="language-elixir"># Input:
# [
#   %{post: %Post{}, last_comment: %Comment{}, reaction: %Reaction{}},
#   ...
# ]
#
# Output:
# [
#   %{post: %Post{}, last_comment: %Comment{reactions: [%Reaction{}, ...]}}
# ]

def restructure(rows) do
  # Elixir code goes here
end
</code></pre>

<p>This is a fair bit of work, but workable and very flexible.</p>

<h2 id="using-json" id="using-json">Using JSON</h2>

<p>We can save some work by returning a tree-like object from the database with <code>json_agg</code>:</p>

<h2 id="add-association-and-preload" id="add-association-and-preload">Add association and preload</h2>

<p>Cannot be done dynamically. Why?</p>

<h2 id="return-json-and-cast" id="return-json-and-cast">Return json and cast</h2>
]]></content:encoded>
      <guid>https://leifmetcalf.com/ecto-hierarchical-queries</guid>
      <pubDate>Fri, 31 Jan 2025 08:04:13 +0000</pubDate>
    </item>
    <item>
      <title>A geometric proof of \\(\sin 2\theta = 2 \sin \theta \cos \theta\\)</title>
      <link>https://leifmetcalf.com/geometric-proof-of-sin-2-theta-2-sin-theta-cos-theta?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[To start with, let&#39;s draw the standard trigonometric unit circle diagram for the angles \\(\theta\\) and \\(2\theta\\):&#xA;!--more--&#xA;&#xA;The area corresponding to \\(\sin \theta \cos \theta\\) is easy to see—it&#39;s the lower right-angle triangle. The area corresponding to \\(\sin 2 \theta\\) is slightly harder to see. Since the height of the the \\(2\theta\\) point is \\(\sin 2 \theta\\) the triangle with base length \\(1\\) will also have area \\(\frac{1}{2} \sin 2 \theta\\). Thus the diagram becomes:&#xA;&#xA;The area of the big triangle is \\(\frac{1}{2} \sin 2 \theta\\) and the area of the small triangle is \\(\frac{1}{2} \sin \theta \cos \theta\\). We want to show that the area of the big triangle is twice the area of the small triangle. The symmetry of the diagram begs for another small triangle to be placed:&#xA;&#xA;The areas of the kite and the triangle are equal, and hence \\(\sin 2 \theta = 2 \sin \theta \cos \theta\\). This is seen by symmetry along the dotted lines below:&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>To start with, let&#39;s draw the standard trigonometric unit circle diagram for the angles \(\theta\) and \(2\theta\):
</p>

<p><img src="https://i.snap.as/Eu7Ns2pe.png" alt=""/></p>

<p>The area corresponding to \(\sin \theta \cos \theta\) is easy to see—it&#39;s the lower right-angle triangle. The area corresponding to \(\sin 2 \theta\) is slightly harder to see. Since the height of the the \(2\theta\) point is \(\sin 2 \theta\) the triangle with base length \(1\) will also have area \(\frac{1}{2} \sin 2 \theta\). Thus the diagram becomes:</p>

<p><img src="https://i.snap.as/Ts5fmge9.png" alt=""/></p>

<p>The area of the big triangle is \(\frac{1}{2} \sin 2 \theta\) and the area of the small triangle is \(\frac{1}{2} \sin \theta \cos \theta\). We want to show that the area of the big triangle is twice the area of the small triangle. The symmetry of the diagram begs for another small triangle to be placed:</p>

<p><img src="https://i.snap.as/73QmClOR.png" alt=""/></p>

<p>The areas of the kite and the triangle are equal, and hence \(\sin 2 \theta = 2 \sin \theta \cos \theta\). This is seen by symmetry along the dotted lines below:</p>

<p><img src="https://i.snap.as/XNTtTs5j.png" alt=""/></p>
]]></content:encoded>
      <guid>https://leifmetcalf.com/geometric-proof-of-sin-2-theta-2-sin-theta-cos-theta</guid>
      <pubDate>Thu, 03 Dec 2020 18:57:54 +0000</pubDate>
    </item>
  </channel>
</rss>