<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>tommyodland.com</title><link>https://tommyodland.com/</link><description/><atom:link href="https://tommyodland.com/feeds/all.rss.xml" rel="self"/><lastBuildDate>Fri, 09 Oct 2026 00:00:00 +0200</lastBuildDate><item><title>Decline Curve Analysis</title><link>https://tommyodland.com/articles/2026/decline-curve-analysis</link><description>&lt;p&gt;&lt;strong&gt;Abstract.&lt;/strong&gt; 
Decline Curve Analysis (&lt;span class="caps"&gt;DCA&lt;/span&gt;) is about fitting parametric curves to petroleum production time series, usually with the goal of forecasting future production. 
&lt;span class="caps"&gt;DCA&lt;/span&gt; dates back to at least the 1940s, and is more accessible than ever with modern optimization methods. 
It is a simple, transparent and fast forecasting method that only requires time series data.&lt;/p&gt;
&lt;p&gt;Still, the statement “let’s use &lt;span class="caps"&gt;DCA&lt;/span&gt;” leaves many details unspecified: how should data be preprocessed? 
Which parametric curve should one use? What kind of loss function is appropriate? How to incorporate uncertainty? 
In this article we provide one set of answers to these questions.&lt;/p&gt;
&lt;div class="text-center mb-4"&gt;
&lt;a class="btn btn-primary btn-lg" download="" href="https://tommyodland.com/files/storage/decline-curve-analysis.pdf" role="button"&gt;Download the &lt;span class="caps"&gt;PDF&lt;/span&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#1-introduction"&gt;1. Introduction&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#11-simplicity-in-forecasting"&gt;1.1 Simplicity in forecasting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#12-a-critique-of-the-literature"&gt;1.2 A critique of the literature&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#2-basic-mathematical-model"&gt;2. Basic mathematical model&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#21-data-preprocessing"&gt;2.1 Data preprocessing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#22-naive-least-squares-fit"&gt;2.2 Naive least-squares fit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#3-curve-models"&gt;3. Curve models&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#31-the-constant-model"&gt;3.1 The constant model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#32-the-exponential-model"&gt;3.2 The exponential model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#33-the-arps-model"&gt;3.3 The Arps model&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#4-curve-fitting"&gt;4. Curve fitting&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#41-weighting-recent-data"&gt;4.1 Weighting recent data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#42-fitting-on-log-scale"&gt;4.2 Fitting on log scale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#43-the-median-curve"&gt;4.3 The median curve&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#44-regularization"&gt;4.4 Regularization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#5-a-statistical-model"&gt;5. A statistical model&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#51-independent-errors"&gt;5.1 Independent errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#52-autocorrelated-errors"&gt;5.2 Autocorrelated errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#53-statistical-model-with-autocorrelation"&gt;5.3 Statistical model with autocorrelation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#54-parameter-inference"&gt;5.4 Parameter inference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#55-forecasting"&gt;5.5 Forecasting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#6-summary"&gt;6. Summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#appendix-stan-code"&gt;Appendix: Stan Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="1-introduction"&gt;1. Introduction&lt;/h2&gt;
&lt;figure style="margin:1.2em auto; max-width:780px; text-align:center;"&gt;
&lt;a href="https://tommyodland.com/images/articles/decline_curve_analysis/DCA_fit_kvitebjørn.png" target="_blank"&gt;
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/decline_curve_analysis/DCA_fit_kvitebjørn.png" style="display:block; margin-left:auto; margin-right:auto; width:95%; max-width:780px;"/&gt;
&lt;/a&gt;
&lt;figcaption style="font-style:italic; font-size:0.9em; margin-top:0.5em;"&gt;
&lt;strong&gt;Figure 1.&lt;/strong&gt; An Arps curve model fit to production data. &lt;span class="caps"&gt;DCA&lt;/span&gt; is most commonly used on individual well time series, but here we perform &lt;span class="caps"&gt;DCA&lt;/span&gt; on the aggregated field level.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Decline Curve Analysis (&lt;span class="caps"&gt;DCA&lt;/span&gt;) is the process of fitting parametric curves to well production time series data. 
The idea dates back to when Arps (1945)&lt;sup id="fnref:arps45"&gt;&lt;a class="footnote-ref" href="#fn:arps45"&gt;11&lt;/a&gt;&lt;/sup&gt; introduced his three-parameter curve model:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\label{eqn:arps} \tag{1}
f(t; q, h, D) = q(1 + hDt)^{-1/h}
\end{equation}&lt;/div&gt;
&lt;p&gt;&lt;span class="caps"&gt;DCA&lt;/span&gt; is simple: it requires no geological, nor any physics-based, model. 
We simply take observed production and extrapolate with a curve model &lt;span class="math"&gt;\(f\)&lt;/span&gt;. 
But simple does not always mean easy: Should we use the well-known Arps curve, or perhaps some other curve model instead? 
Which loss function? How should data be pre-processed? How to handle outliers? How to quantify uncertainty? 
In this article we discuss these questions.&lt;/p&gt;
&lt;p&gt;Equinor (2025)&lt;sup id="fnref:equinor25"&gt;&lt;a class="footnote-ref" href="#fn:equinor25"&gt;12&lt;/a&gt;&lt;/sup&gt; provides an &lt;a href="https://github.com/equinor/decline-curve-analysis/"&gt;open-source Python implementation&lt;/a&gt; of many of the ideas presented in this article.
I hope you apply the ideas (and code) in your own forecasting.&lt;/p&gt;
&lt;h3 id="11-simplicity-in-forecasting"&gt;1.1 Simplicity in forecasting&lt;/h3&gt;
&lt;p&gt;You might be surprised that we, in 2026, discuss what is essentially an eighty year old forecasting method.
Has the world not progressed? Are better and more sophisticated approaches not available?
The answer is nuanced: we’ll first discuss forecasting in general, then forecasting petroleum time series specifically.&lt;/p&gt;
&lt;p&gt;It is not always the case that novel methods lead to better results.
On many problems across industries, simple and well-established methods often produce the best results.&lt;sup id="fnref:fn-sophisticated"&gt;&lt;a class="footnote-ref" href="#fn:fn-sophisticated"&gt;1&lt;/a&gt;&lt;/sup&gt;
Green &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Armstrong (2015)&lt;sup id="fnref:green15"&gt;&lt;a class="footnote-ref" href="#fn:green15"&gt;13&lt;/a&gt;&lt;/sup&gt; reviewed papers on forecasting and found little evidence that complexity improves accuracy.
Fama &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; French (2010)&lt;sup id="fnref:fama10"&gt;&lt;a class="footnote-ref" href="#fn:fama10"&gt;14&lt;/a&gt;&lt;/sup&gt; found no evidence that active management of stock portfolios outperformed the market average.
Yan (2022)&lt;sup id="fnref:yan22"&gt;&lt;a class="footnote-ref" href="#fn:yan22"&gt;15&lt;/a&gt;&lt;/sup&gt; argues that complex methods are often chosen because they signal effort, mastery and innovation: &lt;em&gt;complexity sells, but simplicity is an advantage&lt;/em&gt;.
Simple methods also have an advantage in real-world deployments: they are easier to understand and use, easier to build and scale, and easier to maintain.&lt;/p&gt;
&lt;p&gt;What about forecasting petroleum production specifically?
Are engineers professionals whose expertise and advanced models drastically improve forecasts, or are they more like stock market investors—believing that they outperform the market when they rarely do?
The answer is somewhere in between the two extremes, but the second possibility must be considered.
Bratvold et al. (2020)&lt;sup id="fnref:brat20"&gt;&lt;a class="footnote-ref" href="#fn:brat20"&gt;16&lt;/a&gt;&lt;/sup&gt; showed that early stage forecasts of petroleum projects are systematically optimistic and overconfident.
Odland &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Hollund (2026)&lt;sup id="fnref:odla26"&gt;&lt;a class="footnote-ref" href="#fn:odla26"&gt;17&lt;/a&gt;&lt;/sup&gt; showed that on 70% of wells in a sample of thousands, simple automatic &lt;span class="caps"&gt;DCA&lt;/span&gt; outperformed engineering forecasts.
Unfortunately we do not know the full answer, since there is little public data available for researchers to compare forecasting methods on, and the literature is somewhat disappointing.&lt;/p&gt;
&lt;h3 id="12-a-critique-of-the-literature"&gt;1.2 A critique of the literature&lt;/h3&gt;
&lt;p&gt;I have chosen to not cite many papers related to &lt;span class="caps"&gt;DCA&lt;/span&gt;, since I believe most of it is low-quality.
Novel methods are rarely compared against strong baselines, such as a properly tuned &lt;span class="caps"&gt;DCA&lt;/span&gt;. Train/test splits are often absent or have time leaks.
Code is seldom provided. Evaluation is rarely statistically meaningful: ten or twenty wells is not enough data for a meaningful conclusion.
In summary, basic scientific method is often lacking in papers published on &lt;span class="caps"&gt;DCA&lt;/span&gt; and related topics.&lt;sup id="fnref:fn-experimental"&gt;&lt;a class="footnote-ref" href="#fn:fn-experimental"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;To remedy this, the goal must be clearly stated: &lt;em&gt;we wish to predict future production as accurately as possible&lt;/em&gt;.
Whether the theory is clean or the methods are novel is unimportant if the forecasts are inaccurate.
In a real-world engineering organization, the cost of complexity must be considered: complex methods require more data inputs, are harder to reason about, and have higher operational costs.&lt;/p&gt;
&lt;p&gt;Having said all this, I have nothing against physical theories or fancy methods.
My plea is only that engineers use scientific methodology when choosing forecasting methods: evaluate on many wells, on a test set, benchmarking against a strong baseline.
If nothing else, I hope the ideas in this article provide a baseline to benchmark your own ideas against.&lt;/p&gt;
&lt;h2 id="2-basic-mathematical-model"&gt;2. Basic mathematical model&lt;/h2&gt;
&lt;p&gt;In this section we outline a model that we’ll extend on in two ways: first by curve fitting, then by statistical modeling.
The model is frequency-agnostic, but in reality data typically arrives either daily or monthly. Each day or month is called a &lt;em&gt;period&lt;/em&gt;.
Within each period &lt;span class="math"&gt;\(i\)&lt;/span&gt;, we observe the total &lt;em&gt;production&lt;/em&gt; &lt;span class="math"&gt;\(p_i\)&lt;/span&gt; as well as an &lt;em&gt;uptime&lt;/em&gt; fraction &lt;span class="math"&gt;\(0 &amp;lt; \tau_i \leq 1\)&lt;/span&gt;.
The unit of production is arbitrary. The uptime denotes how much of a time period the well is producing. For instance, if a well was shut down for &lt;span class="math"&gt;\(3\)&lt;/span&gt; out of &lt;span class="math"&gt;\(30\)&lt;/span&gt; days, the uptime would be &lt;span class="math"&gt;\(\tau = 0.9\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="example" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Example 1 (Observed data).&lt;/strong&gt;
If we observe monthly data with structure&lt;/p&gt;
&lt;pre&gt;
period    production    uptime
 2026-01   0.482         0.92
 2026-02   0.435         0.95
 2026-03   0.445         1.00
&lt;/pre&gt;
&lt;p&gt;then &lt;span class="math"&gt;\(\boldsymbol{p} = (0.482, 0.435, 0.445)\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{\tau} = (0.92, 0.95, 1.00)\)&lt;/span&gt;.
If the well is always producing, then &lt;span class="math"&gt;\(\tau_i = 1\)&lt;/span&gt; for all &lt;span class="math"&gt;\(i\)&lt;/span&gt; and the equations simplify.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We define the &lt;em&gt;cumulative uptime&lt;/em&gt; as &lt;span class="math"&gt;\(\xi_i = \sum_{j=1}^i \tau_j\)&lt;/span&gt;.
Suppose we have a decline curve model &lt;span class="math"&gt;\(f(t; \boldsymbol{\theta})\)&lt;/span&gt;, such as the Arps curve, that represents production rate (the units of &lt;span class="math"&gt;\(f\)&lt;/span&gt; are production per time unit).
Ignoring noise and assuming our model is correct:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Production in the first period is &lt;span class="math"&gt;\(p_1 = \int_{0}^{\tau_1} f(t; \boldsymbol{\theta}) \, dt = \int_{\xi_0}^{\xi_1} f(t; \boldsymbol{\theta}) \, dt\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Production in the second period is &lt;span class="math"&gt;\(p_2 = \int_{\tau_1}^{\tau_1 + \tau_2} f(t; \boldsymbol{\theta}) \, dt = \int_{\xi_1}^{\xi_2} f(t; \boldsymbol{\theta}) \, dt\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Production in the third period is &lt;span class="math"&gt;\(p_3 = \int_{\tau_1 + \tau_2}^{\tau_1 + \tau_2 + \tau_3} f(t; \boldsymbol{\theta}) \, dt = \int_{\xi_2}^{\xi_3} f(t; \boldsymbol{\theta}) \, dt\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One approach would be to work with the antiderivative &lt;span class="math"&gt;\(F(t; \boldsymbol{\theta}) = \int_0^t f(z; \boldsymbol{\theta}) \, dz\)&lt;/span&gt;, by setting &lt;span class="math"&gt;\(p_i = F(\xi_i; \boldsymbol{\theta}) - F(\xi_{i-1}; \boldsymbol{\theta})\)&lt;/span&gt;.
Another approach, which we pursue, is to apply the midpoint rule of numerical integration to obtain&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
p_i = \int_{\xi_{i-1}}^{\xi_{i}} f(t; \boldsymbol{\theta}) \, dt
\approx f\left( \frac{\xi_{i-1} + \xi_{i}}{2}; \boldsymbol{\theta} \right) \tau_i.
\end{equation*}&lt;/div&gt;
&lt;p&gt;When &lt;span class="math"&gt;\(f\)&lt;/span&gt; is nearly linear at small time segments, this approximation is very good.&lt;sup id="fnref:fn-midpoint"&gt;&lt;a class="footnote-ref" href="#fn:fn-midpoint"&gt;3&lt;/a&gt;&lt;/sup&gt;
Dividing both sides by &lt;span class="math"&gt;\(\tau_i\)&lt;/span&gt; and introducing &lt;span class="math"&gt;\(t_i = (\xi_{i-1} + \xi_{i}) / 2 = \xi_i - \tau_i / 2\)&lt;/span&gt; as the &lt;em&gt;midpoint&lt;/em&gt;, we obtain a curve fitting problem on &lt;em&gt;production rates&lt;/em&gt; &lt;span class="math"&gt;\(y_i\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y_i = \frac{p_i}{\tau_i} = f\left( t_i; \boldsymbol{\theta} \right)
\end{equation*}&lt;/div&gt;
&lt;h3 id="21-data-preprocessing"&gt;2.1 Data preprocessing&lt;/h3&gt;
&lt;p&gt;We now introduce two preprocessing rules that transform raw data &lt;span class="math"&gt;\((\boldsymbol{p}, \boldsymbol{\tau})\)&lt;/span&gt; into &lt;span class="math"&gt;\((\boldsymbol{t}, \boldsymbol{y}, \boldsymbol{w})\)&lt;/span&gt; for curve fitting. Each &lt;span class="math"&gt;\((t_i, y_i)\)&lt;/span&gt; is a data point and &lt;span class="math"&gt;\(w_i\)&lt;/span&gt; is its associated weight. The choice of rule depends on the question the forecaster wants to answer.
In addition to these two rules, standard data cleaning is needed: missing values, zeros, inconsistencies, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Producing time transform&lt;/strong&gt;. This data preprocessing rule answers the question “If the well has 100% uptime in the future, what will it produce?”
On the horizontal (time) axis, we fit to midpoints and account for uptime by contracting the axis. On the vertical (production rate) axis, we fit to production rates.
Data is weighted by the uptime fraction.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
t_i =  \xi_i - \tau_i / 2
\quad
y_i = \frac{p_i}{\tau_i}
\quad
w_i = \tau_i
\end{align*}&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Calendar time transform&lt;/strong&gt;. This data preprocessing rule answers the question “If the uptime in the future is roughly equal to the uptime in the past, what will the well produce in the future?”
On the horizontal axis, we fit to midpoints within each period without accounting for uptime.
On the vertical axis, we fit to production values. Data is not weighted by the uptime fraction.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
t_i =  i - 0.5
\quad
y_i = p_i
\quad
w_i = 1
\end{align*}&lt;/div&gt;
&lt;p&gt;Notice that the calendar time transform is equivalent to setting &lt;span class="math"&gt;\(\tau_i = 1\)&lt;/span&gt; for all periods &lt;span class="math"&gt;\(i\)&lt;/span&gt; and applying the producing time transform. The crucial difference between the two rules is whether (1) time passes only when the well is producing, or if it (2) passes irrespectively of whether the well is producing or not.&lt;/p&gt;
&lt;div class="example" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Example 2 (Data transformations).&lt;/strong&gt; Suppose &lt;span class="math"&gt;\(\boldsymbol{p} = (100, 50, 25)\)&lt;/span&gt;, &lt;span class="math"&gt;\(\boldsymbol{\tau} = (1.0, 0.5, 0.25)\)&lt;/span&gt; and we use a simple curve model &lt;span class="math"&gt;\(f\left( t; \theta_1 \right) = \theta_1\)&lt;/span&gt; with a least-squares loss function.
The producing time transform yields&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{t} =  (0.5, 1.25, 1.625)
\quad
\boldsymbol{y} = (100, 100, 100)
\quad
\boldsymbol{w} = (1.0, 0.5, 0.25)
\end{align*}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;and the forecast becomes &lt;span class="math"&gt;\(100\)&lt;/span&gt; units of production in the future, since that is what the well will produce if it has 100% uptime in the future. If we instead use the calendar time transform, the forecast would be &lt;span class="math"&gt;\((100 + 50 + 25) / 3 \approx 58.33\)&lt;/span&gt; units per period in the future.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="22-naive-least-squares-fit"&gt;2.2 Naive least-squares fit&lt;/h3&gt;
&lt;p&gt;Having established a mathematical model and data preprocessing rules, we are now in a position where we could use a weighted least-squares loss function and minimize&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\label{eqn:leastsquares} \tag{2}
\mathcal{L}\left( \boldsymbol{\theta} \right) = \sum_i w_i \left( y_i - f\left( t_i; \boldsymbol{\theta} \right)  \right)^2
\end{equation}&lt;/div&gt;
&lt;p&gt;to perform &lt;span class="caps"&gt;DCA&lt;/span&gt;. However, we do not recommend this approach, and will introduce alternatives that improve forecasting accuracy.&lt;/p&gt;
&lt;p&gt;Two directions are possible: we can modify the curve model &lt;span class="math"&gt;\(f\)&lt;/span&gt; or the loss function &lt;span class="math"&gt;\(\mathcal{L}\)&lt;/span&gt;.
While many papers compare curve models, the largest improvements in forecasting accuracy are obtained by modifying the loss function.
Before we venture down that path we will discuss curve models.&lt;/p&gt;
&lt;h2 id="3-curve-models"&gt;3. Curve models&lt;/h2&gt;
&lt;p&gt;The Arps model is the most popular choice of curve model, but other parametric functions can also be used.
In this section we aim to demystify some common curve models, discuss their properties and re-parametrize them so their parameters are expressed as unbounded real numbers.&lt;/p&gt;
&lt;h3 id="31-the-constant-model"&gt;3.1 The constant model&lt;/h3&gt;
&lt;p&gt;The constant model is simply&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\log f\left( t; \theta_1 \right) = \theta_1.
\end{equation*}&lt;/div&gt;
&lt;p&gt;The logarithm ensures that the parameter &lt;span class="math"&gt;\(\theta_1\)&lt;/span&gt; is unbounded and the outputs are positive.
This model should not be used for real-world forecasting, but it is a useful test-case.&lt;/p&gt;
&lt;h3 id="32-the-exponential-model"&gt;3.2 The exponential model&lt;/h3&gt;
&lt;figure style="margin:1.2em auto; max-width:780px; text-align:center;"&gt;
&lt;a href="https://tommyodland.com/images/articles/decline_curve_analysis/landscape_morvin.png" target="_blank"&gt;
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/decline_curve_analysis/landscape_morvin.png" style="display:block; margin-left:auto; margin-right:auto; width:95%; max-width:780px;"/&gt;
&lt;/a&gt;
&lt;figcaption style="font-style:italic; font-size:0.9em; margin-top:0.5em;"&gt;&lt;strong&gt;Figure 2.&lt;/strong&gt; Optimization log-landscape for two parametrizations of the exponential curve. The &lt;span class="math"&gt;&lt;script type="math/tex"&gt;(C, k)&lt;/script&gt;&lt;/span&gt;-parametrization has a thin, narrow valley around the optimum. The &lt;span class="math"&gt;&lt;script type="math/tex"&gt;(\theta_1, \theta_2)&lt;/script&gt;&lt;/span&gt;-parametrization is closer to quadratic. The difference is more pronounced than the figure shows, since the axes of the &lt;span class="math"&gt;&lt;script type="math/tex"&gt;(C, k)&lt;/script&gt;&lt;/span&gt;-parametrization have been scaled for visibility. If unscaled, the valley is so narrow that it is nearly impossible to see on a plot.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;An exponential function such as &lt;span class="math"&gt;\(f(t; C, k) = C \exp(-kt)\)&lt;/span&gt; is a viable &lt;span class="caps"&gt;DCA&lt;/span&gt; candidate.
However, if this parametrization is used, two issues crop up.
The first is that &lt;span class="math"&gt;\(C\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt; must both be positive, so a constrained optimization algorithm must be used.
The second issue is that &lt;span class="math"&gt;\(C\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt; might be on vastly different scales, causing first-order optimization methods to struggle.&lt;/p&gt;
&lt;p&gt;One solution is to scale the input data to mitigate the different scales of &lt;span class="math"&gt;\(C\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt;.
Another solution is to use an alternative parametrization, such as&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\log f(t; \theta_1, \theta_2)  = \theta_1 - \theta_2 - t \exp(-\theta_2)
\end{equation*}&lt;/div&gt;
&lt;p&gt;which avoids both of the problems above.
See Figure 2 for a visualization of the optimization landscapes associated with these two parametrizations.&lt;/p&gt;
&lt;h3 id="33-the-arps-model"&gt;3.3 The Arps model&lt;/h3&gt;
&lt;p&gt;The Arps model is convenient and often matches real-world data.
That being said, the main reason for its popularity is arguably historical entrenchment.
There is nothing inherently superior about the Arps curve, and many papers show equally good forecasts when using other, similar curve models.&lt;sup id="fnref:fn-physical"&gt;&lt;a class="footnote-ref" href="#fn:fn-physical"&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The most common parametrization of the Arps model is&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
f(t; q, h, D) = q(1 + hDt)^{-1/h},
\end{equation*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(q &amp;gt;0\)&lt;/span&gt;, &lt;span class="math"&gt;\(D &amp;gt;0\)&lt;/span&gt; and &lt;span class="math"&gt;\(0 &amp;lt; h \leq 1\)&lt;/span&gt;. When &lt;span class="math"&gt;\(h &amp;lt; 1\)&lt;/span&gt;, the integral is finite and evaluates to&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\int_0^T f(t; q, h, D) \, dt = \frac{q \left[  (1 + ThD)^{\frac{h-1}{h}} - 1 \right]}{(h-1) D}.
\end{equation*}&lt;/div&gt;
&lt;p&gt;This parametrization suffers from the same issues as the naive exponential parametrization, and numerical issues can arise as &lt;span class="math"&gt;\(h \to 0\)&lt;/span&gt; if implemented naively.
An alternative parametrization, due to Lee &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Mallick (2021)&lt;sup id="fnref:lee21"&gt;&lt;a class="footnote-ref" href="#fn:lee21"&gt;18&lt;/a&gt;&lt;/sup&gt;, avoids these issues:&lt;sup id="fnref:fn-overflow"&gt;&lt;a class="footnote-ref" href="#fn:fn-overflow"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\log f(t; \theta_1, \theta_2, \theta_3)
=
\theta_1 - \theta_2 - \left( 1 + e^{-\theta_3} \right) \log \left( 1 + t e^{\theta_3 - \theta_2} \right).
\end{equation*}&lt;/div&gt;
&lt;p&gt;It comes from the following change of variables:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\theta_1 &amp;amp;= \log \left(   q  / ((1 - h) D) \right)  &amp;amp; &amp;amp; h= 1 / (1 + \exp(-\theta_3)) \\
\theta_2 &amp;amp;= \log \left( 1  / ((1 - h) D) \right)    &amp;amp; &amp;amp; D= 1 / ((1 - h) \exp(\theta_2) ) \\
\theta_3 &amp;amp;= \log \left(  h  / (1 - h) \right)       &amp;amp; &amp;amp; q= \exp(\theta_1) (1-h ) D.
\end{align*}&lt;/div&gt;
&lt;p&gt;A few comments are in order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Arps model is equivalent to the generalized Pareto distribution. If you want to experiment with other curve models, look to probability density functions and modify them as needed. Two popular alternatives, not discussed further in this article, are the stretched exponential curve and the Weibull curve.&lt;/li&gt;
&lt;li&gt;The Arps model can be thought of as a relaxation of the definition &lt;span class="math"&gt;\(\exp x := \lim_{n \to \infty} \left(1 + \frac{x}{n}\right)^n\)&lt;/span&gt;, where we choose &lt;span class="math"&gt;\(n = 1/h\)&lt;/span&gt; and instead of taking the limit &lt;span class="math"&gt;\(h \to 0\)&lt;/span&gt; we leave &lt;span class="math"&gt;\(h\)&lt;/span&gt; as a free parameter. As &lt;span class="math"&gt;\(h \to 0\)&lt;/span&gt;, or equivalently as &lt;span class="math"&gt;\(\theta_3 \to -\infty\)&lt;/span&gt;, the Arps model converges to the exponential model.&lt;/li&gt;
&lt;li&gt;Since &lt;span class="math"&gt;\(\int_{0}^{\infty} f(t; \boldsymbol{\theta}) \, dt = \exp (\theta_1)\)&lt;/span&gt;, prior belief about the Estimated Ultimate Recovery (&lt;span class="caps"&gt;EUR&lt;/span&gt;) of a well can be encoded as a prior distribution on &lt;span class="math"&gt;\(\theta_1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Shifting the log-model up or down can be accomplished by changing &lt;span class="math"&gt;\(\theta_1\)&lt;/span&gt;, since &lt;span class="math"&gt;\(\log f(t; \theta_1, \theta_2, \theta_3) + z = \log f(t; \theta_1 + z, \theta_2, \theta_3)\)&lt;/span&gt;. Pointwise P10 and P90 curves can therefore be obtained by specifying &lt;span class="math"&gt;\(\theta_1^{\text{P10}}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\theta_1^{\text{P90}}\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4-curve-fitting"&gt;4. Curve fitting&lt;/h2&gt;
&lt;p&gt;In Section 2.2 we introduced the least squares loss and claimed that it is inappropriate for &lt;span class="caps"&gt;DCA&lt;/span&gt;.
In this section we extend the loss with a non-statistical curve fitting approach.
Later we will consider a statistical perspective.&lt;/p&gt;
&lt;figure style="margin:1.2em auto; max-width:780px; text-align:center;"&gt;
&lt;a href="https://tommyodland.com/images/articles/decline_curve_analysis/weighted_fitting_statfjord.png" target="_blank"&gt;
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/decline_curve_analysis/weighted_fitting_statfjord.png" style="display:block; margin-left:auto; margin-right:auto; width:95%; max-width:780px;"/&gt;
&lt;/a&gt;
&lt;figcaption style="font-style:italic; font-size:0.9em; margin-top:0.5em;"&gt;&lt;strong&gt;Figure 3.&lt;/strong&gt; Unweighted vs. weighted least squares fit on production in the Statfjord field. We use aggregated field data from the Norwegian Offshore Directorate (&lt;a href="https://factpages.sodir.no"&gt;factpages.sodir.no&lt;/a&gt;). It would be more meaningful to look at individual wells, but these data are not public.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="41-weighting-recent-data"&gt;4.1 Weighting recent data&lt;/h3&gt;
&lt;p&gt;When forecasting the future, the recent past matters more than the distant past.
Based on this observation, we introduce a weighting function &lt;span class="math"&gt;\(W(t_i, \gamma)\)&lt;/span&gt; and modify the loss function in &lt;span class="math"&gt;\(\eqref{eqn:leastsquares}\)&lt;/span&gt; to read&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\mathcal{L}\left( \boldsymbol{\theta}; \gamma \right) = \sum_i W(t_i, \gamma) \, w_i \left( y_i - f\left( t_i; \boldsymbol{\theta} \right)  \right)^2,
\end{equation*}&lt;/div&gt;
&lt;p&gt;where the weight function is an exponential decay going back in time, given by&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
W(t_i, \gamma) \propto 2^{(t_i - \max_i t_i) / \gamma}.
\end{equation*}&lt;/div&gt;
&lt;p&gt;The parameter &lt;span class="math"&gt;\(\gamma &amp;gt; 0\)&lt;/span&gt; is the &lt;em&gt;half-life&lt;/em&gt;.
It controls how far back in time we go until a data point receives a relative weight of 50%.
Half-life lets us focus in on recent data without using a hard cutoff, and its effect is shown in Figure 3.&lt;/p&gt;
&lt;p&gt;A good starting point for &lt;span class="math"&gt;\(\gamma\)&lt;/span&gt; is to set it equal to the forecasting horizon.
For instance, if the forecasting horizon is one year into the future, and data has monthly frequency, then &lt;span class="math"&gt;\(\gamma = 12\)&lt;/span&gt; is a good value.
A more sophisticated approach is to treat &lt;span class="math"&gt;\(\gamma\)&lt;/span&gt; as a hyper-parameter and tune it on a test set that matches the forecasting horizon in length.&lt;/p&gt;
&lt;h3 id="42-fitting-on-log-scale"&gt;4.2 Fitting on log scale&lt;/h3&gt;
&lt;p&gt;Often, fitting log-model to log-production is sensible since it penalizes multiplicative errors instead of additive.
With this loss, predicting e.g. 20% over the observed production rate &lt;span class="math"&gt;\(y_i\)&lt;/span&gt; is equally bad, no matter the value of &lt;span class="math"&gt;\(y_i\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\mathcal{L}\left( \boldsymbol{\theta}; \gamma \right) = \sum_i W(t_i, \gamma) \, w_i \left( \log y_i - \log f\left( t_i; \boldsymbol{\theta} \right)  \right)^2
\end{equation*}&lt;/div&gt;
&lt;p&gt;While fitting on log-scale often leads to better out-of-sample predictions, it introduces a theoretical misalignment: If &lt;span class="math"&gt;\(X\)&lt;/span&gt; is a random variable, then &lt;span class="math"&gt;\(\exp \operatorname{mean}_i \log X_i \leq \operatorname{mean}_i X_i\)&lt;/span&gt;.
Fitting in log-space and then exponentiating the result, without accounting for the variance, underestimates the mean.
However, the primary goal is to forecast the test set to high accuracy—a clean theory is not a goal in and of itself.
The effect of fitting in log-space is shown in Figure 4.
Test set performance can help decide whether one should fit in log-space or not.&lt;sup id="fnref:fn-cumulative"&gt;&lt;a class="footnote-ref" href="#fn:fn-cumulative"&gt;6&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;figure style="margin:1.2em auto; max-width:780px; text-align:center;"&gt;
&lt;a href="https://tommyodland.com/images/articles/decline_curve_analysis/original_vs_log_statfjord.png" target="_blank"&gt;
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/decline_curve_analysis/original_vs_log_statfjord.png" style="display:block; margin-left:auto; margin-right:auto; width:95%; max-width:780px;"/&gt;
&lt;/a&gt;
&lt;figcaption style="font-style:italic; font-size:0.9em; margin-top:0.5em;"&gt;&lt;strong&gt;Figure 4.&lt;/strong&gt; The effect of fitting the Arps model in the original space (without taking logarithm) vs. fitting in log space (fitting log-model to log-data).&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="43-the-median-curve"&gt;4.3 The median curve&lt;/h3&gt;
&lt;p&gt;Outliers show up in real-world data for various reasons.
Fitting in log-space dampens their effect.
We can also fit a &lt;em&gt;median&lt;/em&gt; curve instead of a &lt;em&gt;mean&lt;/em&gt; curve.
To accomplish this, we change the &lt;span class="math"&gt;\(2\)&lt;/span&gt; to &lt;span class="math"&gt;\(1\)&lt;/span&gt; in the loss function exponent—minimizing Mean Absolute Error (&lt;span class="caps"&gt;MAE&lt;/span&gt;) instead of Mean Squared Error (&lt;span class="caps"&gt;MSE&lt;/span&gt;).
More generally, we can introduce a hyperparameter &lt;span class="math"&gt;\(1 \leq p \leq 2\)&lt;/span&gt; that we tune by cross-validation:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\mathcal{L}\left( \boldsymbol{\theta}; \gamma, p \right) = \sum_i W(t_i, \gamma) \, w_i \lvert \log y_i - \log f\left( t_i; \boldsymbol{\theta} \right)  \rvert^p
\end{equation*}&lt;/div&gt;
&lt;p&gt;Test-set evaluation can still be measured by &lt;span class="caps"&gt;RMSE&lt;/span&gt;.
Another option is to change the evaluation metric from &lt;span class="caps"&gt;MSE&lt;/span&gt; to &lt;span class="caps"&gt;MAE&lt;/span&gt;.&lt;/p&gt;
&lt;figure style="margin:1.2em auto; max-width:780px; text-align:center;"&gt;
&lt;a href="https://tommyodland.com/images/articles/decline_curve_analysis/mse_vs_mae_morvin.png" target="_blank"&gt;
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/decline_curve_analysis/mse_vs_mae_morvin.png" style="display:block; margin-left:auto; margin-right:auto; width:95%; max-width:780px;"/&gt;
&lt;/a&gt;
&lt;figcaption style="font-style:italic; font-size:0.9em; margin-top:0.5em;"&gt;&lt;strong&gt;Figure 5.&lt;/strong&gt; Fitting a mean curve (minimizing &lt;span class="caps"&gt;MSE&lt;/span&gt;) and a median curve (minimizing &lt;span class="caps"&gt;MAE&lt;/span&gt;) on the same data set. The fit was performed in log-space with exponential data weighting. Notice how the mean curve is influenced by the downward outliers, while the median curve is not. Whether these low-production periods should be considered outliers or not can be determined by cross validation instead of subjective assessment - choose the &lt;span class="math"&gt;&lt;script type="math/tex"&gt;p&lt;/script&gt;&lt;/span&gt; that leads to the best out-of-sample forecasting performance.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="44-regularization"&gt;4.4 Regularization&lt;/h3&gt;
&lt;p&gt;To produce robust forecasts of wells with little data, and help the optimizer converge, we can add a regularization term &lt;span class="math"&gt;\(P(\boldsymbol{\theta})\)&lt;/span&gt; to the loss.
With a quadratic regularization term, the loss becomes:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\mathcal{L}\left( \boldsymbol{\theta}; \gamma, p, \alpha \right) &amp;amp;=
\sum_i W(t_i, \gamma) \, w_i
\lvert \log y_i - \log f\left( t_i; \boldsymbol{\theta} \right)  \rvert^p
+ \alpha P(\boldsymbol{\theta})
\\
P(\boldsymbol{\theta}) &amp;amp;= \left( \boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}}  \right)^T
\boldsymbol{\Sigma}^{-1}
\left( \boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}}  \right)
\end{align*}&lt;/div&gt;
&lt;p&gt;Regularization strength is controlled by the hyperparameter &lt;span class="math"&gt;\(\alpha \geq 0\)&lt;/span&gt;, which can be set manually or tuned by cross validation.
A simple but effective way of determining the prior mean &lt;span class="math"&gt;\(\boldsymbol{\mu}_{\boldsymbol{\theta}}\)&lt;/span&gt; is by a two stage process: first do a &lt;em&gt;pilot-fit&lt;/em&gt; where all wells are concatenated into one data set, then use the observed mean &lt;span class="math"&gt;\(\boldsymbol{\mu}_{\boldsymbol{\theta}}\)&lt;/span&gt; to inform each individual well.&lt;sup id="fnref:fn-partialpooling"&gt;&lt;a class="footnote-ref" href="#fn:fn-partialpooling"&gt;7&lt;/a&gt;&lt;/sup&gt;
The matrix &lt;span class="math"&gt;\(\boldsymbol{\Sigma}^{-1}\)&lt;/span&gt; can be determined by a pilot fit, or it can simply be set to the identity matrix.&lt;/p&gt;
&lt;p&gt;Taking all these ideas into account (data weighting, fitting on log-scale, allowing a median curve and adding a regularization term), the final loss function becomes&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\label{eqn:dcaloss} \tag{3}
\begin{aligned}
\mathcal{L}(\boldsymbol{\theta}; \gamma, p, \alpha) &amp;amp;=
\sum_i W(t_i, \gamma)\, w_i
\lvert \log y_i - \log f\left( t_i; \boldsymbol{\theta} \right)  \rvert^p
+ \alpha P(\boldsymbol{\theta})
\\
W(t_i, \gamma) &amp;amp;\propto 2^{(t_i - \max_i t_i)/\gamma}
\\
P(\boldsymbol{\theta}) &amp;amp;= \left(\boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}}\right)^T \boldsymbol{\Sigma}^{-1} \left(\boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}}\right)
\end{aligned}
\end{equation}&lt;/div&gt;
&lt;p&gt;The weights &lt;span class="math"&gt;\(W(t_i, \gamma)\)&lt;/span&gt; can be scaled so the final weight is &lt;span class="math"&gt;\(1\)&lt;/span&gt;, or so that &lt;span class="math"&gt;\(\sum_i W(t_i, \gamma)\, w_i = \sum_i w_i\)&lt;/span&gt;.
Least-squares loss is a special case of this more general loss function.
A practitioner can choose which of the ideas presented in this section to implement or discard, depending on their forecasting problem.
For more tips and tricks related to curve fitting in general, see Odland (2025).&lt;sup id="fnref:odland2025"&gt;&lt;a class="footnote-ref" href="#fn:odland2025"&gt;22&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id="5-a-statistical-model"&gt;5. A statistical model&lt;/h2&gt;
&lt;p&gt;In this section we present a statistical model that forecasts future production with uncertainty.
Before we go down this path: remember that forecasted uncertainty relies on the assumption that the statistical model is correct.
If the model is unreasonable, the forecast will be unreasonable too.&lt;/p&gt;
&lt;p&gt;The curve fitting approach explored in Section 4 is forgiving when forecasts are aggregated, since errors tend to cancel when forecasting a group of wells: if one well is over-forecasted, the next might be under-forecasted, and so on.
This is not the case for uncertainty.
In each individual well, uncertainty is often under-estimated, and &lt;em&gt;these errors do not cancel when wells are aggregated&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="51-independent-errors"&gt;5.1 Independent errors&lt;/h3&gt;
&lt;p&gt;Our first attempt at a statistical model assumes independent errors.
We can specify a forward model, that we can simulate from, as&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\log \left( y_i \right) &amp;amp;= \log \left( f\left(t_i; \boldsymbol{\theta} \right) \right) + \sqrt{\tau_i} \sigma \epsilon_i \\
\epsilon_i &amp;amp;\sim N(0, 1).
\end{align*}&lt;/div&gt;
&lt;p&gt;When we employ additive errors on the log-scale, we assume multiplicative errors and guarantee that predicted production is positive.&lt;sup id="fnref:fn-logscale-stat"&gt;&lt;a class="footnote-ref" href="#fn:fn-logscale-stat"&gt;8&lt;/a&gt;&lt;/sup&gt;
Once parameters are estimated, the expected value can be computed as&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\label{eqn:expected_value_arps} \tag{4}
p_i / \tau_i
= \mathbb{E} \left[
\exp \left( \log \left( f\left(t_i; \boldsymbol{\theta} \right) \right) +
\sqrt{\tau_i} \sigma \epsilon_i \right)
\right]
= f\left(t_i; \boldsymbol{\theta} \right) \exp \left( \tau_i \sigma^2 / 2 \right),
\end{equation}&lt;/div&gt;
&lt;p&gt;where the last equality is due to the fact that &lt;span class="math"&gt;\(\mathbb{E} \left[ \exp N(\mu, \sigma^2) \right] = \exp \left( \mu + \sigma^2 / 2\right)\)&lt;/span&gt;.
When forecasting with uncertainty, Monte-Carlo simulation should be performed since the sum of log-normal distributions does not have an analytical equation.&lt;/p&gt;
&lt;p&gt;The square-root term &lt;span class="math"&gt;\(\sqrt{\tau_i}\)&lt;/span&gt; comes from thinking of noise as a Wiener process within each period on the log scale: &lt;span class="math"&gt;\(W(\xi_i) - W(\xi_{i-1}) \sim N(0, \tau_i \sigma^2)\)&lt;/span&gt;.
Alternatively, we may think of summing &lt;span class="math"&gt;\(n\)&lt;/span&gt; independent normal variables: the standard deviation grows like &lt;span class="math"&gt;\(\sqrt{n}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;With this model, observing (or forecasting) data on a fine grid and a coarse grid is not equivalent.
The issue is that the sum of two log-normals is not log-normal. Assuming &lt;span class="math"&gt;\(f\)&lt;/span&gt; is approximately constant over two periods and forecasting the expected value yields&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{2 periods of length 1: }&amp;amp;
\frac{p_i }{1} + \frac{p_{i+1} }{1}
 =
f \exp \left( \sigma^2 / 2 \right)
+
f  \exp \left( \sigma^2 / 2 \right) \\
\text{1 period of length 2: }&amp;amp;
\frac{p_j }{2}
 =
f \exp \left( 2 \sigma^2 / 2 \right)
\end{align*}&lt;/div&gt;
&lt;p&gt;The requirement &lt;span class="math"&gt;\(p_i  + p_{i+1} = p_j\)&lt;/span&gt; translates into &lt;span class="math"&gt;\(2 f \exp \left( \sigma^2 / 2 \right) = 2 f \exp \left( 2 \sigma^2 / 2 \right)\)&lt;/span&gt;, and this only holds when &lt;span class="math"&gt;\(\sigma\)&lt;/span&gt; is small.
In practice every value of &lt;span class="math"&gt;\(\tau_i\)&lt;/span&gt; is often similar, and we can use a (possibly weighted) mean &lt;span class="math"&gt;\(\bar{\boldsymbol{\tau}}\)&lt;/span&gt; when forecasting before interpolating onto a unit grid.&lt;/p&gt;
&lt;h3 id="52-autocorrelated-errors"&gt;5.2 Autocorrelated errors&lt;/h3&gt;
&lt;figure style="margin:1.2em auto; max-width:780px; text-align:center;"&gt;
&lt;a href="https://tommyodland.com/images/articles/decline_curve_analysis/AR1_OU_process.png" target="_blank"&gt;
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/decline_curve_analysis/AR1_OU_process.png" style="display:block; margin-left:auto; margin-right:auto; width:95%; max-width:780px;"/&gt;
&lt;/a&gt;
&lt;figcaption style="font-style:italic; font-size:0.9em; margin-top:0.5em;"&gt;&lt;strong&gt;Figure 6.&lt;/strong&gt; An &lt;span class="caps"&gt;AR&lt;/span&gt;(1) process sampled at equidistant discrete time, and a discretized &lt;span class="caps"&gt;OU&lt;/span&gt; process sampled at non-equidistant times with equivalent parameters. Notice how the process stays above or below trend for a while, but eventually reverts back to trend level. This phenomenon is observed in real-world production data.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Simulating from the model in Section 5.1 does not yield realistic results, because wells rarely exhibit independent errors.
Therefore, forecasting from the model does not yield realistic uncertainty either.
Real data is typically above or below trend for longer time periods, as shown in Figure 6.&lt;/p&gt;
&lt;p&gt;On discrete, equidistant time intervals, we can model correlated noise using an &lt;span class="caps"&gt;AR&lt;/span&gt;(1) process centered around zero:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
X_{i} = \phi X_{i-1} + \sigma \epsilon_i, \quad \epsilon_i \sim N(0, 1).
\end{equation*}&lt;/div&gt;
&lt;p&gt;The parameter &lt;span class="math"&gt;\(\sigma &amp;gt; 0\)&lt;/span&gt; determines how much the next value &lt;span class="math"&gt;\(X_i\)&lt;/span&gt; is perturbed away from the previous value, while &lt;span class="math"&gt;\(0 \leq \phi &amp;lt; 1\)&lt;/span&gt; determines mean reversion.
The first conditional moments of &lt;span class="math"&gt;\(X_i\)&lt;/span&gt; are given by&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\mathbb{E}\left[ X_{i} \mid X_{i-n} \right] = X_{i-n} \phi^n
\quad
\operatorname{Var}\left[ X_{i} \mid X_{i-n} \right] = \sigma^2 \frac{1 - \phi^{2n}}{1 - \phi^2}
\end{equation*}&lt;/div&gt;
&lt;p&gt;Taking the limit &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt; we obtain the long-running steady state moments&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\mathbb{E}\left[ X_{i} \right] = 0
\quad
\operatorname{Var}\left[ X_{i}  \right] = \frac{\sigma^2}{1 - \phi^2}
\end{equation*}&lt;/div&gt;
&lt;p&gt;Since we allow uptime &lt;span class="math"&gt;\(\tau_i\)&lt;/span&gt; to be different in each time period, our time intervals are not equidistant.
Assuming that uptime does not vary too much, one approximation is to use &lt;span class="math"&gt;\(X_{i} = \phi X_{i-1} + \sqrt{\tau_i} \sigma \epsilon_i\)&lt;/span&gt;, but this process grows unbounded if &lt;span class="math"&gt;\(\tau_i \to \infty\)&lt;/span&gt;.
A more appropriate model can be found by discretizing the Ornstein-Uhlenbeck (&lt;span class="caps"&gt;OU&lt;/span&gt;) process, which is a continuous time analogue of &lt;span class="caps"&gt;AR&lt;/span&gt;(1).
The stochastic differential equation that defines the &lt;span class="caps"&gt;OU&lt;/span&gt; process is&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
d x_{i} = -\vartheta x_{i} dt + \sigma dW_i,
\end{equation*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(W_i\)&lt;/span&gt; is a Wiener process.
The solution yields the expectation and variance on an arbitrary, non-equidistant grid with spacing &lt;span class="math"&gt;\(\tau_i\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\mathbb{E}\left[ x_{i} \mid x_{i-1} \right] =
x_{i-1} e^{-\tau_i \vartheta}
\quad
\operatorname{Var}\left[ x_{i} \mid x_{i-1}  \right] =
\sigma^2 \frac{ \left( 1 - e^{-2 \vartheta \tau_i}  \right)}{2 \vartheta}.
\end{equation*}&lt;/div&gt;
&lt;p&gt;In the limit &lt;span class="math"&gt;\(\tau_i \to \infty\)&lt;/span&gt;, we obtain the stationary moments&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\mathbb{E}\left[ x_{i}  \right] =
0
\quad
\operatorname{Var}\left[ x_{i} \right] =
 \frac{ \sigma^2}{2 \vartheta}.
\end{equation*}&lt;/div&gt;
&lt;p&gt;See Holý &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Tomanová (2025) for more information about these equations.&lt;sup id="fnref:holy18"&gt;&lt;a class="footnote-ref" href="#fn:holy18"&gt;20&lt;/a&gt;&lt;/sup&gt;
Notice that if &lt;span class="math"&gt;\(\vartheta \tau_i \approx 0\)&lt;/span&gt;, then we can write &lt;span class="math"&gt;\(e^{-2 \vartheta \tau_i} \approx 1 - 2 \vartheta \tau_i\)&lt;/span&gt; and the variance becomes &lt;span class="math"&gt;\(\operatorname{Var}\left[ x_{i} \mid x_{i-1}  \right] \approx \sigma^2 \tau_i\)&lt;/span&gt;, which leads to a &lt;span class="math"&gt;\(\sqrt{\tau_i}-\)&lt;/span&gt;approximation of the standard deviation.&lt;/p&gt;
&lt;p&gt;The exact discretized noise model, which we will use in the following model, is&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
X_i = \exp(-\vartheta \tau_i) X_{i-1} +
\sigma \sqrt{\frac{1 - \exp(-2 \vartheta \tau_i)}{2 \vartheta}} \epsilon_i
\end{equation*}&lt;/div&gt;
&lt;h3 id="53-statistical-model-with-autocorrelation"&gt;5.3 Statistical model with autocorrelation&lt;/h3&gt;
&lt;p&gt;The final statistical model uses the discretized &lt;span class="caps"&gt;OU&lt;/span&gt; process, but with a Generalized Normal (&lt;span class="caps"&gt;GN&lt;/span&gt;) shock term &lt;span class="math"&gt;\(\epsilon_i\)&lt;/span&gt;.
The reason for the &lt;span class="caps"&gt;GN&lt;/span&gt; distribution is the same as allowing &lt;span class="math"&gt;\(1 \leq p \leq 2\)&lt;/span&gt; in the exponent in the curve fit model: it makes inference robust to outliers and enables predicting more heavy-tailed production.
We retain the &lt;span class="caps"&gt;OU&lt;/span&gt; variance structure as a convenient parametrization rather than a consequence of the stochastic differential equation.&lt;/p&gt;
&lt;figure style="margin:1.2em auto; max-width:780px; text-align:center;"&gt;
&lt;a href="https://tommyodland.com/images/articles/decline_curve_analysis/simulated_well.png" target="_blank"&gt;
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/decline_curve_analysis/simulated_well.png" style="display:block; margin-left:auto; margin-right:auto; width:95%; max-width:780px;"/&gt;
&lt;/a&gt;
&lt;figcaption style="font-style:italic; font-size:0.9em; margin-top:0.5em;"&gt;&lt;strong&gt;Figure 7.&lt;/strong&gt; A simulation from the statistical model with autocorrelated errors.
Notice that the simulation tends to stay above or below the mean value (defined by the Arps curve) before regressing back. This kind of behavior is also seen in real-life wells.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The forward model, which we can simulate from, becomes&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\log \left( y_i \right) &amp;amp;= \log \left( f\left(t_i; \boldsymbol{\theta} \right) \right) + \eta_i \\
\eta_i &amp;amp;= \exp(-\vartheta \tau_i) \eta_{i-1} + \sigma \sqrt{\frac{1 - \exp(-2 \vartheta \tau_i)}{2 \vartheta}} \epsilon_i \tag{5} \label{eqn:statistical_model} \\
\epsilon_i &amp;amp;\sim \operatorname{GN}(0, 1, \beta).
\end{align}&lt;/div&gt;
&lt;p&gt;The first equation defines the trend, the second the correlated noise process and the third the shock.
The shock term &lt;span class="math"&gt;\(\epsilon_i\)&lt;/span&gt; is drawn with unit scale; the actual scale is applied as a multiplier outside, using &lt;span class="math"&gt;\(\alpha X \sim \operatorname{GN}(0, \alpha, \beta)\)&lt;/span&gt; for &lt;span class="math"&gt;\(X \sim \operatorname{GN}(0, 1, \beta)\)&lt;/span&gt;.
In the special case &lt;span class="math"&gt;\(\beta=2\)&lt;/span&gt; the &lt;span class="caps"&gt;GN&lt;/span&gt; reduces to a Normal distribution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Initial condition&lt;/strong&gt;.
Starting at the first index &lt;span class="math"&gt;\(i=1\)&lt;/span&gt;, what is the correct value of the first noise term &lt;span class="math"&gt;\(\eta_1\)&lt;/span&gt;?
To answer this, we assume that the long running, stationary distribution of &lt;span class="math"&gt;\(\eta\)&lt;/span&gt; is normal.
This is a fair approximation as long as &lt;span class="math"&gt;\(\exp(-\vartheta \tau_i)\)&lt;/span&gt; is not too close to zero (in that case the stationary distribution is closer to a &lt;span class="caps"&gt;GN&lt;/span&gt;-distribution).
This approximation only applies to the first data point, and is therefore often not very important.&lt;/p&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(\phi := \exp(-\vartheta)\)&lt;/span&gt;.
The variance of &lt;span class="caps"&gt;GN&lt;/span&gt; is given by &lt;span class="math"&gt;\(\operatorname{Var} \left[  \operatorname{GN}(0, 1, \beta) \right] = \Gamma(3/\beta) / \Gamma(1/\beta)\)&lt;/span&gt;, where &lt;span class="math"&gt;\(\Gamma\)&lt;/span&gt; is the Gamma function.
The variance of the steady state can be found by taking variance of the recursion&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
 \operatorname{Var} \left[  \eta_i \right]
 &amp;amp;=
 \operatorname{Var} \left[  \phi^{\tau_i} \eta_{i-1} \right] +
  \operatorname{Var} \left[ \sigma \sqrt{\frac{1 - \phi^{2\tau_i}}{2 \vartheta}} \epsilon_i \right]
  \\
 &amp;amp;=
\phi^{2 \tau_i} \operatorname{Var} \left[  \eta_{i-1} \right] +
\sigma^2 \frac{1 - \phi^{2\tau_i}}{2 \vartheta}
\operatorname{Var} \left[  \epsilon_i \right] \\
&amp;amp;=
\phi^{2 \tau_i} \operatorname{Var} \left[  \eta_{i-1} \right] +
\sigma^2 \frac{1 - \phi^{2\tau_i}}{2 \vartheta}
\frac{\Gamma(3/\beta)}{\Gamma(1/\beta)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;At steady state we must have &lt;span class="math"&gt;\(\operatorname{Var} \left[  \eta_i \right] = \operatorname{Var} \left[  \eta_{i-1} \right] = \operatorname{Var} \left[  \eta \right]\)&lt;/span&gt;.
Substituting this into the equation above and solving yields&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\operatorname{Var} \left[  \eta \right]
=
\frac{\sigma^2}{2 \vartheta} \frac{\Gamma(3/\beta)}{\Gamma(1/\beta)}.
\end{equation*}&lt;/div&gt;
&lt;p&gt;To summarize, for the initial condition we assume&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\label{eqn:statistical_model_initial} \tag{6}
\eta_1 \sim N \left( 0, \sigma_1^2 \right) \text{ with }
\sigma_1^2 = \frac{\sigma^2}{2 \vartheta} \frac{\Gamma(3/\beta)}{\Gamma(1/\beta)}.
\end{equation}&lt;/div&gt;
&lt;p&gt;Using Equations &lt;span class="math"&gt;\(\eqref{eqn:statistical_model}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\eqref{eqn:statistical_model_initial}\)&lt;/span&gt; we can simulate from the model.
One such simulation is shown in Figure 7.
In other words: given parameters, we can generate data.
We now discuss the inverse problem: determining parameters, given data.&lt;/p&gt;
&lt;figure style="margin:1.2em auto; max-width:780px; text-align:center;"&gt;
&lt;a href="https://tommyodland.com/images/articles/decline_curve_analysis/arps_bayesian.png" target="_blank"&gt;
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/decline_curve_analysis/arps_bayesian.png" style="display:block; margin-left:auto; margin-right:auto; width:95%; max-width:780px;"/&gt;
&lt;/a&gt;
&lt;figcaption style="font-style:italic; font-size:0.9em; margin-top:0.5em;"&gt;&lt;strong&gt;Figure 8.&lt;/strong&gt; The first figure shows the posterior mean curve. The second figure shows samples from the posterior distribution of &lt;span class="math"&gt;&lt;script type="math/tex"&gt;\boldsymbol{\theta}&lt;/script&gt;&lt;/span&gt;, plotted as curves. The third shows posterior predictive simulations: each line shows a simulation using parameters drawn from the posterior of &lt;span class="math"&gt;&lt;script type="math/tex"&gt;\boldsymbol{\Theta}&lt;/script&gt;&lt;/span&gt;. A slight weakness of the model is revealed: while history is mostly smooth with some downward outliers, the noise of the model captures this as higher overall noise. The fourth figure shows pointwise percentiles over the cumulative forecasts with uncertainty, estimating the distribution of &lt;span class="caps"&gt;EUR&lt;/span&gt; as &lt;span class="math"&gt;&lt;script type="math/tex"&gt;t \to \infty&lt;/script&gt;&lt;/span&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="54-parameter-inference"&gt;5.4 Parameter inference&lt;/h3&gt;
&lt;p&gt;This section explains how to infer parameters from the model defined by &lt;span class="math"&gt;\(\eqref{eqn:statistical_model}\)&lt;/span&gt; above.
Collecting all parameters into &lt;span class="math"&gt;\(\boldsymbol{\Theta} := (\boldsymbol{\theta}, \vartheta, \sigma, \beta)\)&lt;/span&gt; and defining &lt;span class="math"&gt;\(z_i := \log (y_i)\)&lt;/span&gt; to ease notation, the log-likelihood becomes&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\log P(z_1, z_2, z_3, \ldots \mid \boldsymbol{\Theta})
&amp;amp;=
\log \left( P(z_1 \mid \boldsymbol{\Theta}) \,
P(z_2 \mid z_1, \boldsymbol{\Theta} \,)
P(z_3 \mid z_2, \boldsymbol{\Theta} ) \, \ldots  \right)
 \\
&amp;amp;= \log \left( P(z_1 \mid \boldsymbol{\Theta} ) \right) +
\log \left( P(z_2 \mid z_1, \boldsymbol{\Theta}) \right) +
\log \left( P(z_3 \mid z_2, \boldsymbol{\Theta}) \right) + \ldots
\end{align*}&lt;/div&gt;
&lt;p&gt;The log-likelihood of the &lt;span class="math"&gt;\(i\)&lt;/span&gt;&lt;span class="quo"&gt;‘&lt;/span&gt;th term is&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\log P \left( z_i \mid z_{i-1}, \boldsymbol{\Theta} \right)
&amp;amp;=
\log \operatorname{GN} \left( z_i \mid \mu_i, \alpha_i, \beta \right) \\
\mu_i &amp;amp;= \log \left( f\left(t_i; \boldsymbol{\theta} \right) \right) + \exp(-\vartheta \tau_i) \eta_{i-1} \\
\alpha_i &amp;amp;= \sigma \sqrt{\frac{1 - \exp(-2 \vartheta \tau_i)}{2 \vartheta}},
\end{align*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(\eta_{i-1} = z_{i-1} - \log \left( f\left(t_{i-1}; \boldsymbol{\theta} \right) \right)\)&lt;/span&gt;. Setting &lt;span class="math"&gt;\(\phi := \exp(-\vartheta)\)&lt;/span&gt;, we can write:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\mu_i = \log \left( f\left(t_i; \boldsymbol{\theta} \right) \right) + \phi ^ {\tau_i}
\left[ z_{i-1} - \log \left( f\left(t_{i-1}; \boldsymbol{\theta} \right) \right) \right]
\end{equation*}&lt;/div&gt;
&lt;p&gt;This gives us &lt;span class="math"&gt;\(\mu_i\)&lt;/span&gt; in terms of previous data, and is analogous to Prais–Winsten estimation in &lt;span class="caps"&gt;AR&lt;/span&gt;(1) regression.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Initial condition&lt;/strong&gt;.
The log-likelihood of the first term is&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\log P \left( z_1 \mid \boldsymbol{\Theta} \right)
&amp;amp;=
\log N \left( z_1 \mid \mu_1, \sigma_1^2 \right) \\
\mu_1 &amp;amp;= \log \left( f\left(t_1; \boldsymbol{\theta} \right) \right)  \quad
\sigma_1^2 = \frac{\sigma^2}{2 \vartheta} \frac{\Gamma(3/\beta)}{\Gamma(1/\beta)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;Some general comments on parameter inference:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We use likelihood weights &lt;span class="math"&gt;\(W(t_i, \gamma) \, w_i\)&lt;/span&gt;, as defined in &lt;span class="math"&gt;\(\eqref{eqn:dcaloss}\)&lt;/span&gt;, to account for observation quality and recency. The &lt;span class="math"&gt;\(w_i\)&lt;/span&gt; factor was given in Section 2.1, and corresponds to either &lt;span class="math"&gt;\(\tau_i\)&lt;/span&gt; in the producing time transform, or simply &lt;span class="math"&gt;\(1\)&lt;/span&gt; in the calendar time transform.&lt;/li&gt;
&lt;li&gt;The model parameters &lt;span class="math"&gt;\(\boldsymbol{\theta}\)&lt;/span&gt; become non-identifiable as &lt;span class="math"&gt;\(\exp(-\vartheta \tau_i) \to 1\)&lt;/span&gt;. If the error term &lt;span class="math"&gt;\(\boldsymbol{\eta}\)&lt;/span&gt; does not revert back to the trend, but instead grows unbounded (like Brownian motion), then observed data can be explained by &lt;em&gt;any&lt;/em&gt; decline curve plus a drifting error term. A prior should be put on &lt;span class="math"&gt;\(\vartheta\)&lt;/span&gt; to keep it away from zero.&lt;/li&gt;
&lt;li&gt;There are two main approaches to infer parameters: maximum likelihood point estimation or sampling the posterior with &lt;span class="caps"&gt;MCMC&lt;/span&gt;. Parameter uncertainty leads to more meaningful probabilistic forecasts, but is slower and requires a &lt;span class="caps"&gt;MCMC&lt;/span&gt; library.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="55-forecasting"&gt;5.5 Forecasting&lt;/h3&gt;
&lt;p&gt;In this section we spell out some details about how to use the statistical model to forecast and how to obtain percentile and mean production rate curves.
See also Equinor (2025).&lt;sup id="fnref2:equinor25"&gt;&lt;a class="footnote-ref" href="#fn:equinor25"&gt;12&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Probabilistic forecasting&lt;/strong&gt;.
Algorithm 1 demonstrates how to generate future production simulations.
From simulation trajectories &lt;span class="math"&gt;\(\{\boldsymbol{p}_j\}_{j=1}^{S}\)&lt;/span&gt; we can extract pointwise percentiles and expected values for each future period &lt;span class="math"&gt;\(i\)&lt;/span&gt;, as well as cumulative sums, &lt;span class="caps"&gt;EUR&lt;/span&gt;, etc.&lt;/p&gt;
&lt;p&gt;Assuming &lt;span class="math"&gt;\(n\)&lt;/span&gt; observed periods, we generate &lt;span class="math"&gt;\(S\)&lt;/span&gt; simulations that each go &lt;span class="math"&gt;\(T\)&lt;/span&gt; periods into the future.
Instead of sampling the posterior, it is possible to use the maximum a posteriori (&lt;span class="caps"&gt;MAP&lt;/span&gt;) estimate of parameters &lt;span class="math"&gt;\(\boldsymbol{\Theta}\)&lt;/span&gt;.
Instead of setting &lt;span class="math"&gt;\(\tau \gets 1\)&lt;/span&gt; (unit spacing) on the future grid, it is possible to use a weighted&lt;sup id="fnref:fn-weights-forecast"&gt;&lt;a class="footnote-ref" href="#fn:fn-weights-forecast"&gt;9&lt;/a&gt;&lt;/sup&gt; mean to estimate a &lt;span class="math"&gt;\(\tau\)&lt;/span&gt; and use that for future simulations.
For instance, if &lt;span class="math"&gt;\(\tau \approx 2/3\)&lt;/span&gt; on historical data, we can use the same value of &lt;span class="math"&gt;\(\tau\)&lt;/span&gt; when forecasting, then interpolate onto a unit grid to forecast period by period.&lt;/p&gt;
&lt;div class="algorithm" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Algorithm 1 (Posterior predictive simulation).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Require:&lt;/strong&gt; Observed data &lt;span class="math"&gt;\(\{(t_i, y_i)\}_{i=1}^{n}\)&lt;/span&gt;, posterior &lt;span class="math"&gt;\(p(\boldsymbol{\Theta} \mid \text{data})\)&lt;/span&gt;; forecast horizon &lt;span class="math"&gt;\(T\)&lt;/span&gt;; simulations &lt;span class="math"&gt;\(S\)&lt;/span&gt;.  &lt;br/&gt;
&lt;strong&gt;Ensure:&lt;/strong&gt; Simulation trajectories &lt;span class="math"&gt;\(\boldsymbol{p}_1, \boldsymbol{p}_2, \ldots, \boldsymbol{p}_S\)&lt;/span&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;for&lt;/strong&gt; &lt;span class="math"&gt;\(j = 1, 2, \ldots, S\)&lt;/span&gt; &lt;strong&gt;do&lt;/strong&gt;   &lt;em&gt;(outer loop: posterior predictive draws)&lt;/em&gt;&lt;ol&gt;
&lt;li&gt;Sample &lt;span class="math"&gt;\(\boldsymbol{\Theta}_j\)&lt;/span&gt; from the posterior   &lt;em&gt;(sample with &lt;span class="caps"&gt;MCMC&lt;/span&gt;, or use &lt;span class="caps"&gt;MAP&lt;/span&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\eta \gets \log(y_n) - \log\!\big(f(t_n;\boldsymbol{\theta}_j)\big)\)&lt;/span&gt;   &lt;em&gt;(initial &lt;span class="math"&gt;\(\eta\)&lt;/span&gt;-value)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\tau \gets 1\)&lt;/span&gt;   &lt;em&gt;(future grid has unit spacing)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\boldsymbol{p}_j \gets \boldsymbol{0} \in \mathbb{R}^{T}\)&lt;/span&gt;   &lt;em&gt;(allocate production vector)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;for&lt;/strong&gt; &lt;span class="math"&gt;\(k = 1, 2, \ldots, T\)&lt;/span&gt; &lt;strong&gt;do&lt;/strong&gt;   &lt;em&gt;(inner loop: future periods)&lt;/em&gt;&lt;ol&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\epsilon \sim \mathrm{GN}(0,\,1,\,\beta_j)\)&lt;/span&gt;   &lt;em&gt;(sample shock term)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\phi_j \gets \exp(-\vartheta_j \tau)\)&lt;/span&gt;   &lt;em&gt;(redefine for notational clarity)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\eta \gets \phi_j \eta + \sigma_j \sqrt{\left( 1 - \phi_j^2 \right) / \left( 2 \vartheta_j \right)} \, \epsilon\)&lt;/span&gt;   &lt;em&gt;(update noise &lt;span class="math"&gt;\(\eta\)&lt;/span&gt;)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(t \gets t_n + k \tau\)&lt;/span&gt;   &lt;em&gt;(compute &lt;span class="math"&gt;\(t_i\)&lt;/span&gt; on the forecast grid)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(p_{j, n} \gets \tau \exp\left( \log f(t; \boldsymbol{\theta}_j) + \eta \right)\)&lt;/span&gt;   &lt;em&gt;(forecast in log-space, then exponentiate)&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Percentile curves&lt;/strong&gt;.
Recall from Section 3.3 that a vertical shift in log-space of the Arps curve &lt;span class="math"&gt;\(f\)&lt;/span&gt; can be expressed as adding to the &lt;span class="math"&gt;\(\theta_1\)&lt;/span&gt; parameter:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\log f(t; \theta_1, \theta_2, \theta_3) + z = \log f(t; \theta_1 + z, \theta_2, \theta_3).
\end{equation*}&lt;/div&gt;
&lt;p&gt;At steady state, the noise process &lt;span class="math"&gt;\(\eta\)&lt;/span&gt; has variance given by &lt;span class="math"&gt;\(\eqref{eqn:statistical_model_initial}\)&lt;/span&gt;.
Assuming that &lt;span class="math"&gt;\(\eta\)&lt;/span&gt; is Normal, we can derive percentile curves and an expected value curve.
Displaying P5, P50, P95 curves, along with the expected curve, is meaningful when engineers quality control production rate curves.&lt;sup id="fnref:fn-percentile-sum"&gt;&lt;a class="footnote-ref" href="#fn:fn-percentile-sum"&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class="example" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Example 3 (P95 Arps curve).&lt;/strong&gt;
The P95 of a Normal distribution is located at &lt;span class="math"&gt;\(1.645\)&lt;/span&gt; standard deviations.
Given &lt;span class="caps"&gt;MAP&lt;/span&gt; parameter estimates &lt;span class="math"&gt;\((\hat{\theta}_1, \hat{\sigma}, \hat{\beta}, \hat{\vartheta})\)&lt;/span&gt; we can create a P95 curve:&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{equation*}
\theta_1^{\text{P95}} = \hat{\theta}_1 + 1.645 \hat{\sigma}
\sqrt{\frac{\Gamma(3/\hat{\beta})}{2 \hat{\vartheta} \Gamma(1/\hat{\beta}) }}.
\end{equation*}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;Notice that the P50 curve is simply &lt;span class="math"&gt;\(\theta_1^{\text{P50}} = \hat{\theta}_1\)&lt;/span&gt;.
Using &lt;span class="caps"&gt;MCMC&lt;/span&gt;, one could compute percentiles over &lt;span class="math"&gt;\(\{(\theta_1 + \eta)\}_{j=1}^S\)&lt;/span&gt;, where &lt;span class="math"&gt;\(\eta\)&lt;/span&gt; is defined as in &lt;span class="math"&gt;\(\eqref{eqn:statistical_model_initial}\)&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="example" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Example 4 (Expected Arps curve).&lt;/strong&gt;
Assume that &lt;span class="math"&gt;\(\eta \sim N \left( 0, \hat{\sigma}_1^2 \right)\)&lt;/span&gt;, where &lt;span class="math"&gt;\(\hat{\sigma}_1^2\)&lt;/span&gt; is given by &lt;span class="math"&gt;\(\eqref{eqn:statistical_model_initial}\)&lt;/span&gt;.
Analogous to &lt;span class="math"&gt;\(\eqref{eqn:expected_value_arps}\)&lt;/span&gt;, but with steady-state &lt;span class="math"&gt;\(\eta\)&lt;/span&gt; as the noise term, we obtain:&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{align*}
p_i / \tau_i
= \mathbb{E} \left[
\exp \left( \log \left( f\left(t_i; \hat{\boldsymbol{\theta}} \right) \right) + \eta \right)
\right]
&amp;amp;= f\left(t_i; \hat{\boldsymbol{\theta}} \right) \exp \left(
 \hat{\sigma}_1^2 / 2
\right) \\
&amp;amp;= \exp \left( \log \left( f(t_i; \hat{\boldsymbol{\theta}} ) \right)
+
\hat{\sigma}_1^2 / 2
\right).
\end{align*}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;In other words: in expectation, adding a Normal &lt;span class="math"&gt;\(\eta\)&lt;/span&gt; in log-space is equivalent to shifting &lt;span class="math"&gt;\(\theta_1\)&lt;/span&gt; by &lt;span class="math"&gt;\(\hat{\sigma}_1^2 / 2\)&lt;/span&gt;.
The expected pointwise production rate is determined by an Arps curve with parameter&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{equation*}
\theta_1^{\text{EXP}} = \hat{\theta}_1 + \hat{\sigma}_1^2 / 2
=
\hat{\theta}_1 +
\frac{\hat{\sigma}^2 \Gamma(3/\hat{\beta})}{4 \hat{\vartheta} \Gamma(1/\hat{\beta})},
\end{equation*}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;where in the last equality we expanded the definition of &lt;span class="math"&gt;\(\hat{\sigma}_1\)&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="6-summary"&gt;6. Summary&lt;/h2&gt;
&lt;p&gt;Eighty years after Arps (1945),&lt;sup id="fnref2:arps45"&gt;&lt;a class="footnote-ref" href="#fn:arps45"&gt;11&lt;/a&gt;&lt;/sup&gt; &lt;span class="caps"&gt;DCA&lt;/span&gt; is still a powerful forecasting method despite its simplicity; or perhaps because of it.
In this article we walked through the choices that turn the phrase “let’s use &lt;span class="caps"&gt;DCA&lt;/span&gt;” into a concrete model, and presented ideas on data preprocessing, re-parametrization of curve models, up-weighting recent observations and fitting in log-space.
We presented two main directions: (1) curve fitting and (2) a statistical model to forecast with uncertainty.
The individual ideas are far from novel, but together they form a baseline that is hard to beat.&lt;/p&gt;
&lt;h2 id="appendix-stan-code"&gt;Appendix: Stan Code&lt;/h2&gt;
&lt;p&gt;Below is Stan code for the statistical model with autocorrelation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;functions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Log of the reparametrized Arps curve (theta-parametrization)&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;log_arps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;theta1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;theta2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;theta3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// log(1 + t * exp(x)) = log_sum_exp(0, log(t) + x)&lt;/span&gt;
        &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;log_term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;log_sum_exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;theta3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;theta2&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;theta1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;theta2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;theta3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;log_term&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Log-density of the Generalized Normal distribution&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;generalized_normal_lpdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;beta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;lgamma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beta&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;beta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Conditional OU variance&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;ou_conditional_variance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;sigma_sq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;vartheta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1 - exp(x) = -expm1(x)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sigma_sq&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;expm1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vartheta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tau&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="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vartheta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kn"&gt;data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                &lt;span class="c1"&gt;// Number of observations&lt;/span&gt;
    &lt;span class="kt"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// Production rates y_i&lt;/span&gt;
    &lt;span class="kt"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// Midpoint times t_i&lt;/span&gt;
    &lt;span class="kt"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Uptime fractions 0 &amp;lt; tau_i &amp;lt;= 1&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;gamma_hl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Half-life (same time units as t)&lt;/span&gt;
    &lt;span class="kt"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;mu_theta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// Prior mean for (theta1, theta2, theta3)&lt;/span&gt;
    &lt;span class="kt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;Sigma_theta_inv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Prior precision (inverse covariance)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kn"&gt;transformed data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;log_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Pre-compute log production rates&lt;/span&gt;

    &lt;span class="c1"&gt;// Pre-compute likelihood weights&lt;/span&gt;
    &lt;span class="kt"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tau&lt;/span&gt; &lt;span class="o"&gt;.*&lt;/span&gt; &lt;span class="nb"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&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="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;gamma_hl&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Normalization&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kn"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Arps curve parameters (all unbounded)&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;theta1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;theta2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;theta3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// OU noise process parameters&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;vartheta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// Mean-reversion rate&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// Diffusion scale of OU process&lt;/span&gt;
    &lt;span class="kt"&gt;real&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&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;upper&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;beta_gn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// GN shape: 2 = Normal, 1 = Laplace&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kn"&gt;model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// PRIORS&lt;/span&gt;
    &lt;span class="c1"&gt;// Multivariate normal prior on theta&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;theta1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theta2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theta3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&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;multi_normal_prec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mu_theta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Sigma_theta_inv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Prior on vartheta: must stay away from 0 for identifiability&lt;/span&gt;
    &lt;span class="c1"&gt;// As vartheta -&amp;gt; 0, the OU process becomes Brownian motion and theta&lt;/span&gt;
    &lt;span class="c1"&gt;// becomes non-identifiable.&lt;/span&gt;
    &lt;span class="n"&gt;vartheta&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// exp(-vartheta) not too close to 0 or 1&lt;/span&gt;

    &lt;span class="c1"&gt;// Weakly informative priors on sigma and beta_gn&lt;/span&gt;
    &lt;span class="n"&gt;sigma&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;lognormal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beta_gn&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&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;beta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// beta = 2 more likely than beta = 1&lt;/span&gt;

    &lt;span class="c1"&gt;// LIKELIHOOD&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// --- First observation (initial condition) ---&lt;/span&gt;
        &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;log_f_prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_arps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;theta1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theta2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theta3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;var_stationary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigma&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="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;vartheta&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;tgamma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;beta_gn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;tgamma&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;beta_gn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;sigma1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var_stationary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;target +=&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;normal_lpdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;log_f_prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// --- Subsequent observations ---&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;log_f_i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_arps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&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;theta1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theta2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theta3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// OU discretization&lt;/span&gt;
            &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;phi_i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;vartheta&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tau&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="c1"&gt;// Conditional mean: Prais-Winsten form&lt;/span&gt;
            &lt;span class="c1"&gt;// mu_i = log f(t_i) + phi_i * (log y_{i-1} - log f(t_{i-1}))&lt;/span&gt;
            &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;eta_prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_y&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="mf"&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;log_f_prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;mu_i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_f_i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;phi_i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;eta_prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="c1"&gt;// Conditional std dev from OU transition&lt;/span&gt;
            &lt;span class="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;cond_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ou_conditional_variance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;vartheta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tau&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="kt"&gt;real&lt;/span&gt; &lt;span class="n"&gt;alpha_i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cond_var&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// weighted GN log-likelihood&lt;/span&gt;
            &lt;span class="k"&gt;target +=&lt;/span&gt; &lt;span class="n"&gt;weights&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;*&lt;/span&gt; &lt;span class="n"&gt;generalized_normal_lpdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_y&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="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;mu_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha_i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;beta_gn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;log_f_prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;log_f_i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&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;!-- ===================== FOOTNOTES ===================== --&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:fn-sophisticated"&gt;
&lt;p&gt;It is simultaneously true that sophisticated methods lead to better results on many problems too. &lt;a class="footnote-backref" href="#fnref:fn-sophisticated" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-experimental"&gt;
&lt;p&gt;This article is also guilty of not having a strong experimental section. Creating such a section without high-quality, open data is hard. The reader is referred to Odland &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Hollund (2026) for some experimental results on these ideas. This is not a peer reviewed paper, and I am advocating for simple &lt;span class="caps"&gt;DCA&lt;/span&gt;—the antithesis of published papers arguing for fancy models. &lt;a class="footnote-backref" href="#fnref:fn-experimental" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-midpoint"&gt;
&lt;p&gt;The midpoint rule underestimates for convex, decreasing curves. &lt;a class="footnote-backref" href="#fnref:fn-midpoint" title="Jump back to footnote 3 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-overflow"&gt;
&lt;p&gt;The final factor &lt;span class="math"&gt;\(\log ( 1 + t e^{\theta_3 - \theta_2} )\)&lt;/span&gt; can overflow if &lt;span class="math"&gt;\(\theta_3 - \theta_2\)&lt;/span&gt; is large. In a practical implementation, one should use the log-sum-exp trick or apply the identity &lt;span class="math"&gt;\(\log (1 + t e^x) = x + \log (t + e^{-x})\)&lt;/span&gt; when &lt;span class="math"&gt;\(x\)&lt;/span&gt; is large. &lt;a class="footnote-backref" href="#fnref:fn-overflow" title="Jump back to footnote 4 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-physical"&gt;
&lt;p&gt;Some literature insists that certain physical regimes, e.g. transient flow in low-permeability systems or gas reservoirs where compressibility changes with pressure, correspond to certain values of &lt;span class="math"&gt;\(h\)&lt;/span&gt; in the Arps curve. But this has no bearing on forecasting: fixing &lt;span class="math"&gt;\(h\)&lt;/span&gt; based on estimated reservoir characteristics instead of leaving it open as a much-needed degree of freedom when fitting to data almost surely lead to worse forecasts. The physical justifications are often either post-hoc explanations for why certain fitted values of &lt;span class="math"&gt;\(h\)&lt;/span&gt; appear plausible, or circular reasoning. &lt;a class="footnote-backref" href="#fnref:fn-physical" title="Jump back to footnote 5 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-cumulative"&gt;
&lt;p&gt;It is sometimes suggested that &lt;em&gt;fitting&lt;/em&gt; cumulative production, or rate as a function of cumulative production, is an alternative to fitting rates as a function of time. However, the error structure such a model imposes is difficult to justify, since errors accumulate across observations. I have yet to encounter a clear empirical or theoretical case for any advantage over fitting rates. I take no issue with &lt;em&gt;plotting&lt;/em&gt; these functions. &lt;a class="footnote-backref" href="#fnref:fn-cumulative" title="Jump back to footnote 6 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-partialpooling"&gt;
&lt;p&gt;A more theoretically appealing approach is to do &lt;em&gt;partial pooling&lt;/em&gt; of parameters with a hyper-prior; see Gelman &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Hill (2006). The open-source &lt;span class="caps"&gt;ADCA&lt;/span&gt; Python package (Equinor, 2025) instead performs a simple two stage process with a pilot fit, since it is faster and does not require Markov Chain Monte Carlo (&lt;span class="caps"&gt;MCMC&lt;/span&gt;). &lt;a class="footnote-backref" href="#fnref:fn-partialpooling" title="Jump back to footnote 7 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-logscale-stat"&gt;
&lt;p&gt;In curve fitting, we can choose to fit on log-scale or not. In the statistical framework, we must work on log scale to ensure positive predictions if errors are Normal. &lt;a class="footnote-backref" href="#fnref:fn-logscale-stat" title="Jump back to footnote 8 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-weights-forecast"&gt;
&lt;p&gt;Using the same exponentially decaying weights as we used elsewhere in this article. &lt;a class="footnote-backref" href="#fnref:fn-weights-forecast" title="Jump back to footnote 9 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-percentile-sum"&gt;
&lt;p&gt;Beware that the sum of percentiles is not the percentile of a sum. We cannot use the independent, pointwise P95 production rates to estimate the P95 of the total production. The probability of a well producing at the 95-th percentile in every future period is vanishingly small. To compute the percentile of the sum, use Monte-Carlo simulation as sketched in Algorithm 1. &lt;a class="footnote-backref" href="#fnref:fn-percentile-sum" title="Jump back to footnote 10 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:arps45"&gt;
&lt;p&gt;Arps, &lt;span class="caps"&gt;J. J.&lt;/span&gt;(1945). “Analysis of Decline Curves.” &lt;em&gt;Transactions of the &lt;span class="caps"&gt;AIME&lt;/span&gt;&lt;/em&gt;, 160, 228–247. &lt;a href="https://doi.org/10.2118/945228-G"&gt;doi:10.2118/945228-G&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:arps45" title="Jump back to footnote 11 in the text"&gt;↩&lt;/a&gt;&lt;a class="footnote-backref" href="#fnref2:arps45" title="Jump back to footnote 11 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:equinor25"&gt;
&lt;p&gt;Equinor (2025). &lt;em&gt;decline-curve-analysis&lt;/em&gt; [Source Code]. &lt;a href="https://github.com/equinor/decline-curve-analysis"&gt;https://github.com/equinor/decline-curve-analysis&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:equinor25" title="Jump back to footnote 12 in the text"&gt;↩&lt;/a&gt;&lt;a class="footnote-backref" href="#fnref2:equinor25" title="Jump back to footnote 12 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:green15"&gt;
&lt;p&gt;Green, &lt;span class="caps"&gt;K. C.&lt;/span&gt; &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Armstrong, &lt;span class="caps"&gt;J. S.&lt;/span&gt;(2015). “Simple versus complex forecasting: The evidence.” &lt;em&gt;Journal of Business Research&lt;/em&gt;, 68, 1678–1685. &lt;a class="footnote-backref" href="#fnref:green15" title="Jump back to footnote 13 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fama10"&gt;
&lt;p&gt;Fama, &lt;span class="caps"&gt;E. F.&lt;/span&gt; &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; French, &lt;span class="caps"&gt;K. R.&lt;/span&gt;(2010). “Luck versus Skill in the Cross-Section of Mutual Fund Returns.” &lt;em&gt;The Journal of Finance&lt;/em&gt;, 65(5), 1915–1947. &lt;a href="https://doi.org/10.1111/j.1540-6261.2010.01598.x"&gt;doi:10.1111/j.1540-6261.2010.01598.x&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:fama10" title="Jump back to footnote 14 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:yan22"&gt;
&lt;p&gt;Yan, Z. (2022). “Simplicity is An Advantage but Sadly Complexity Sells Better.” &lt;em&gt;eugeneyan.com&lt;/em&gt;. &lt;a href="https://eugeneyan.com/writing/simplicity/"&gt;https://eugeneyan.com/writing/simplicity/&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:yan22" title="Jump back to footnote 15 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:brat20"&gt;
&lt;p&gt;Bratvold, R. B., Mohus, E., Petutschnig, D. &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Bickel, E. (2020). “Production Forecasting: Optimistic and Overconfident - Over and Over Again.” &lt;em&gt;&lt;span class="caps"&gt;SPE&lt;/span&gt; Reservoir Evaluation &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Engineering&lt;/em&gt;, 23, 799–810. &lt;a class="footnote-backref" href="#fnref:brat20" title="Jump back to footnote 16 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:odla26"&gt;
&lt;p&gt;Odland, T. &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Hollund, &lt;span class="caps"&gt;K. U.&lt;/span&gt;(2026). “Automated Decline Curve Analysis - &lt;span class="caps"&gt;DCA&lt;/span&gt; from a Data Science Perspective.” &lt;em&gt;&lt;span class="caps"&gt;EAGE&lt;/span&gt; Annual Conference &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Exhibition&lt;/em&gt;, 1–5. &lt;a href="https://doi.org/10.3997/2214-4609.202639021"&gt;doi:10.3997/2214-4609.202639021&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:odla26" title="Jump back to footnote 17 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:lee21"&gt;
&lt;p&gt;Lee, &lt;span class="caps"&gt;S. Y.&lt;/span&gt; &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Mallick, B. (2021). “Bayesian Hierarchical Modeling: Application Towards Production Results in the Eagle Ford Shale of South Texas.” &lt;em&gt;Sankhya B&lt;/em&gt;. &lt;a href="https://doi.org/10.1007/s13571-020-00245-8"&gt;doi:10.1007/s13571-020-00245-8&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:lee21" title="Jump back to footnote 18 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:sodir"&gt;
&lt;p&gt;Norwegian Offshore Directorate. &lt;em&gt;Open Data from the Norwegian Offshore Directorate&lt;/em&gt;. &lt;a href="https://factpages.sodir.no"&gt;https://factpages.sodir.no&lt;/a&gt;. Licensed under the Norwegian Licence for Open Government Data (&lt;span class="caps"&gt;NLOD&lt;/span&gt;) 2.0. &lt;a class="footnote-backref" href="#fnref:sodir" title="Jump back to footnote 19 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:holy18"&gt;
&lt;p&gt;Holý, V. &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Tomanová, P. (2025). “Estimation of Ornstein-Uhlenbeck Process Using Ultra-High-Frequency Data with Application to Intraday Pairs Trading Strategy.” arXiv:1811.09312. &lt;a href="https://doi.org/10.48550/arXiv.1811.09312"&gt;doi:10.48550/arXiv.1811.09312&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:holy18" title="Jump back to footnote 20 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:gelman2006"&gt;
&lt;p&gt;Gelman, A. &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Hill, J. (2006). &lt;em&gt;Data Analysis Using Regression and Multilevel/Hierarchical Models&lt;/em&gt;. Cambridge University Press. &lt;a class="footnote-backref" href="#fnref:gelman2006" title="Jump back to footnote 21 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:odland2025"&gt;
&lt;p&gt;Odland, T. (2025). “A Recipe for Curve Fitting.” &lt;a href="https://tommyodland.com/articles/2025/a-recipe-for-curve-fitting/"&gt;https://tommyodland.com/articles/2025/a-recipe-for-curve-fitting/&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref:odland2025" title="Jump back to footnote 22 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 09 Oct 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-10-09:/articles/2026/decline-curve-analysis</guid><category>articles</category><category>mathematics</category><category>statistics</category></item><item><title>Minimal models and dumb deployments</title><link>https://tommyodland.com/articles/2026/minimal-models-and-dumb-deployments</link><description>&lt;p&gt;The most common mistake I encounter in practical data science work is a certain type of unwarranted complexity in model choice and deployment.
All organizations, but especially those that are not technology companies, should avoid this if they can.
As Dijkstra said in the 70s: “&lt;em&gt;The art of programming is the art of organizing complexity, of mastering multitude and avoiding its bastard chaos as effectively as possible.&lt;/em&gt;“&lt;/p&gt;
&lt;p&gt;Let’s admit it: when it comes to data science, many organizations have underwhelming track records.
Startups and &lt;span class="caps"&gt;FAANG&lt;/span&gt;-like companies might be different, but in the &lt;em&gt;typical&lt;/em&gt; organization, initiatives tend to become messy.
Data science is not unique in this regard: the average corporate codebase, data warehouse, internal documentation, etc. is often not that great either.&lt;/p&gt;
&lt;p&gt;Still, data science is &lt;em&gt;especially&lt;/em&gt; plagued by complexity because it sits on top of, and depends on, other systems.
Bad design, conceptual degradation, backwards-incompatible changes, and lack of documentation tends to accumulate and cause problems.
The paper &lt;a href="https://research.google/pubs/machine-learning-the-high-interest-credit-card-of-technical-debt/"&gt;Machine Learning: The High Interest Credit Card of Technical Debt&lt;/a&gt; lists some concrete reasons why data science is hard in practice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So how should an organization approach its first data science initiatives?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="avoid-complexity"&gt;Avoid complexity&lt;/h2&gt;
&lt;p&gt;Organizations often trip up because they overcomplicate matters; they focus on the wrong things at the wrong time.
Some inexperienced organizations immediately go the “&lt;em&gt;best practice&lt;/em&gt;” route:
multiple deployment environments, elaborate monitoring, automatic re-training, &lt;span class="caps"&gt;API&lt;/span&gt;-endpoints, clusters, etc.
Often this is not needed.&lt;/p&gt;
&lt;p&gt;The same faulty thinking tends to afflict the models too.
On regression tasks, boosting models with thousands of base learners is the default choice.
Hyperparameters (which are often in conflict with each other or are slight variations of the same basic idea) are all at once tuned uncritically by fancy Bayesian optimization algorithms that take ages to run.
Dozens or hundreds of input features are used, whether they are meaningfully predictive or not.&lt;/p&gt;
&lt;p&gt;The resulting model is slow to train, hard to explain and difficult to maintain.
This leads to mountains of busywork for data scientists.&lt;/p&gt;
&lt;h2 id="aim-for-simplicity"&gt;Aim for simplicity&lt;/h2&gt;
&lt;p&gt;The average organization should choose a &lt;em&gt;ridiculously simple&lt;/em&gt; model as the starting point.
On regression tasks, this means a linear model or a decision tree.
Then it should be “deployed” in the dumbest way possible.
If a batch run on a weekly schedule is good enough, then just run it manually from a laptop.
If real-time or a nightly run is needed, convert the model to an &lt;span class="caps"&gt;SQL&lt;/span&gt; statement.
Follow &lt;a href="https://doi.org/10.1371/journal.pcbi.1005510"&gt;“good enough” practices&lt;/a&gt;, but do nothing more.
Then give it a few months and see if it delivers any value to the organization.
This is commandment 5 (Keep it simple) in my &lt;a href="https://tommyodland.com/articles/2024/the-ten-commandments-of-data-science"&gt;ten commandments of data science&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s illustrate this with the &lt;a href="https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_california_housing.html"&gt;California housing dataset&lt;/a&gt;.
The goal is to use eight features to predict house prices.
For reference, a dummy model that predicts the mean achieves an out-of-sample Root Mean Squared Error (&lt;span class="caps"&gt;RMSE&lt;/span&gt;) of 1.17, while a boosting model (widely considered the best choice on tabular data) achieves 0.64.
These models give realistic upper and lower bounds on the &lt;span class="caps"&gt;RMSE&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_models/rmse_comparison.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;"/&gt;&lt;/p&gt;
&lt;h3 id="linear-regression"&gt;Linear regression&lt;/h3&gt;
&lt;p&gt;Linear regression achieves an &lt;span class="caps"&gt;RMSE&lt;/span&gt; of 0.83, which is two thirds of the way between the dummy and the boosting model.
The Lasso variant selects four out of eight features.
The resulting model can be deployed anywhere, for instance as an &lt;span class="caps"&gt;SQL&lt;/span&gt; expression in a database: &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;SELECT&lt;/span&gt;
&lt;span class="w"&gt;  &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;393414&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;MedInc&lt;/span&gt;&lt;span class="p"&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="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;016651&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;HouseAge&lt;/span&gt;&lt;span class="p"&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="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="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;003222&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;AveOccup&lt;/span&gt;&lt;span class="p"&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="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="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;021127&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;Latitude&lt;/span&gt;&lt;span class="p"&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;797172&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="n"&gt;predicted_price&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;california_housing_data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This model is super interpretable, so the business side of the organization will be delighted when they inevitably wish to understand the model.
Predictive power can be improved by adding non-linear transformations of the features and interaction terms, but we won’t pursue that here.&lt;/p&gt;
&lt;h3 id="decision-trees"&gt;Decision trees&lt;/h3&gt;
&lt;p&gt;Do you need interactions and/or non-linearities?
But you still want an interpretable model that can be deployed anywhere?
Use a simple decision tree!
The tree below achieves an &lt;span class="caps"&gt;RMSE&lt;/span&gt; of 0.84, which is very similar to the linear model’s &lt;span class="caps"&gt;RMSE&lt;/span&gt;, and it only uses two features.
Here is the decision tree written in &lt;span class="caps"&gt;SQL&lt;/span&gt;:&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;SELECT&lt;/span&gt;
&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MedInc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MedInc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;07&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&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="mi"&gt;36&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;ELSE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AveOccup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&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="mi"&gt;37&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&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="mi"&gt;79&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;ELSE&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="mi"&gt;88&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="k"&gt;ELSE&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MedInc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;82&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AveOccup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&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="mi"&gt;74&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;39&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;ELSE&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="mi"&gt;56&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;ELSE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;predicted_price&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;california_housing_data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Like the regression model, the tree is extremely interpretable.
Below it is shown as a figure.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_models/simple_regression_tree.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 650px;"/&gt;&lt;/p&gt;
&lt;h2 id="two-approaches-to-improvement"&gt;Two approaches to improvement&lt;/h2&gt;
&lt;p&gt;Broadly speaking there are two approaches to improving any situation.
One is to start from an abstract (and perhaps unattainable) ideal and work backwards, e.g. ask yourself “How does Google do data science?” and then try to emulate it.
The other approach is to start where you are, assess your resources and skills honestly, and ask “What is a small step I can take to improve?”
Both methods have their place, but the second one is often more concrete and less risky.
Practical project experience helps a lot in balancing the two.
Many data science projects fail because complexity is “spent” on &lt;a href="https://mcfunley.com/choose-boring-technology"&gt;the wrong things&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sometimes a simple model with a dumb deployment is not only a good first step, but the best place to end up too.
It’s fast to build, requires little upkeep, is interpretable and has few data dependencies.
On many problems performance will match a complex approach.
It might be unglamorous and unfashionable, but it will quietly keep working—and boy can it save you money and time.&lt;/p&gt;
&lt;p&gt;We’ll end with another Dijkstra quote, my personal favorite:
“&lt;em&gt;Simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better.&lt;/em&gt;“&lt;/p&gt;
&lt;!--
https://arxiv.org/abs/1811.10154
https://arxiv.org/pdf/math/0606441
https://gwern.net/doc/ai/tabular/1993-holte.pdf
https://proceedings.neurips.cc/paper/2015/file/86df7dcfd896fcaf2674f757a2463eba-Paper.pdf
https://mcfunley.com/choose-boring-technology
https://developers.google.com/machine-learning/guides/rules-of-ml/
https://danluu.com/simple-architectures/

### Further reading

- Paper: [Stop Explaining Black Box Machine Learning Models for High Stakes Decisions and Use Interpretable Models Instead](https://arxiv.org/abs/1811.10154)
- Paper: [Hidden Technical Debt in Machine Learning Systems](https://proceedings.neurips.cc/paper/2015/file/86df7dcfd896fcaf2674f757a2463eba-Paper.pdf)
- Blog post: [Simplicity is An Advantage but Sadly Complexity Sells Better](https://eugeneyan.com/writing/simplicity/)
--&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 11 Sep 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-09-11:/articles/2026/minimal-models-and-dumb-deployments</guid><category>articles</category><category>datascience</category></item><item><title>Notes on Ensemble Smoothers</title><link>https://tommyodland.com/articles/2026/notes-on-ensemble-smoothers</link><description>&lt;p&gt;Ensemble smoother algorithms are used to approximate a posterior distribution.
Given a prior over parameters, a forward model (e.g. a petroleum reservoir model) and real-world observations: what is the posterior over the parameters?
Ensemble-based algorithms describe the posterior with a set of samples, and the most common use-case is when the forward model is black-box and expensive to evaluate.&lt;/p&gt;
&lt;p&gt;The notes are organized as follows: first we examine the Gauss-linear case, where we can compute an analytical posterior in arbitrarily high dimensions.
The equation for the Gaussian posterior is the starting point for all ensemble methods.
We derive the Ensemble Smoother (&lt;span class="caps"&gt;ES&lt;/span&gt;)—an algorithm that retains some properties of the Gauss-linear case.
Finally we examine EnIF, which is similar to &lt;span class="caps"&gt;ES&lt;/span&gt; but works with precision matrices rather than covariances, and leans on the statistical theory of high-dimensional estimation.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-gaussian-posterior"&gt;The Gaussian posterior&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#exact-posterior-distribution-pboldsymbolx-mid-boldsymbold"&gt;Exact posterior distribution \(p(\boldsymbol{x} \mid \boldsymbol{d})\)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-ensemble-smoother"&gt;The Ensemble Smoother&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#computation"&gt;Computation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-ensemble-information-filter"&gt;The Ensemble Information Filter&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#computation_1"&gt;Computation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="math"&gt;$$
\newcommand{\R}{\mathbb{R}}
\newcommand{\Z}{\mathbb{Z}}
\newcommand{\N}{\mathbb{N}}
$$&lt;/div&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The Ensemble Kalman Filter was introduced by Evensen (1994)&lt;sup id="fnref:evensen1994"&gt;&lt;a class="footnote-ref" href="#fn:evensen1994"&gt;9&lt;/a&gt;&lt;/sup&gt; and the Ensemble Smoother was introduced by Van Leeuwen and Evensen (1996)&lt;sup id="fnref:vanleeuwen1996"&gt;&lt;a class="footnote-ref" href="#fn:vanleeuwen1996"&gt;10&lt;/a&gt;&lt;/sup&gt;.
Four years later, in Burgers et al. (1998)&lt;sup id="fnref:burgers1998"&gt;&lt;a class="footnote-ref" href="#fn:burgers1998"&gt;11&lt;/a&gt;&lt;/sup&gt;, it was shown that the original update equation was wrong—the covariance did not match in the Gauss-linear case.&lt;sup id="fnref:fn-innovation"&gt;&lt;a class="footnote-ref" href="#fn:fn-innovation"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;New methods were proposed to deal with non-linearity.
One such variant is the Iterative Ensemble Smoother (&lt;span class="caps"&gt;IES&lt;/span&gt;) of Chen and Oliver (2012&lt;sup id="fnref:chen2012"&gt;&lt;a class="footnote-ref" href="#fn:chen2012"&gt;12&lt;/a&gt;&lt;/sup&gt;; 2013&lt;sup id="fnref:chen2013"&gt;&lt;a class="footnote-ref" href="#fn:chen2013"&gt;13&lt;/a&gt;&lt;/sup&gt;).
The idea behind &lt;span class="caps"&gt;IES&lt;/span&gt; is to move towards the maximum of the posterior in several small steps instead of a single large one, like a reduced step length optimization method.
Another variant is the Ensemble Smoother with Multiple Data Assimilation (&lt;span class="caps"&gt;ESMDA&lt;/span&gt;), proposed in Emerick and Reynolds (2013)&lt;sup id="fnref:emerick2013"&gt;&lt;a class="footnote-ref" href="#fn:emerick2013"&gt;14&lt;/a&gt;&lt;/sup&gt;.
It also makes several small steps, but shortens step length implicitly by increasing the covariance matrix of the observations.
Either way, the core idea is that linearizing, taking a small step, re-linearizing, taking another step, etc. is better than one linearization followed by one large step.&lt;/p&gt;
&lt;p&gt;In high dimensions with few samples, empirical correlations are mostly noise and very little signal.
This phenomenon is called &lt;em&gt;spurious correlations&lt;/em&gt;, and all the methods above suffer from it.
Many procedures have been proposed to alleviate it, such as setting all correlations that are below &lt;span class="math"&gt;\(3 / \sqrt{r}\)&lt;/span&gt; to zero (Vossepoel et al., 2025)&lt;sup id="fnref:vossepoel2025"&gt;&lt;a class="footnote-ref" href="#fn:vossepoel2025"&gt;15&lt;/a&gt;&lt;/sup&gt;.
Another idea is to look towards the statistics of high-dimensional models, see for instance Hastie et al. (2009, Ch. 18)&lt;sup id="fnref:hastie2009"&gt;&lt;a class="footnote-ref" href="#fn:hastie2009"&gt;16&lt;/a&gt;&lt;/sup&gt;.
This is what Lunde (2025)&lt;sup id="fnref:lunde2025"&gt;&lt;a class="footnote-ref" href="#fn:lunde2025"&gt;17&lt;/a&gt;&lt;/sup&gt; does in the Ensemble Information Filter (EnIF), which uses graph properties to estimate sparse precision matrices and high-dimensional regression to estimate a sparse linearization of the forward model.&lt;/p&gt;
&lt;p&gt;The papers above can be a bit tough to read:
notation can be a bit inconsistent, some proofs are not as detailed as I’d like, sometimes there is no code to look at, etc.
That is why I have decided to write these notes; maybe they can help clear up some confusion.
If nothing else, you’ll get a second perspective.
You can find well-documented and production-ready Python code for many of these algorithms at the GitHub repository &lt;a href="https://github.com/equinor/iterative_ensemble_smoother/"&gt;github.com/equinor/iterative_ensemble_smoother/&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-gaussian-posterior"&gt;The Gaussian posterior&lt;/h2&gt;
&lt;p&gt;With a Gaussian prior, a Gaussian noise model and a linear forward model, we’re in the happy Gauss-linear case where an analytical expression for the posterior can be derived.
In this section we establish notation and illustrate several expressions for the posterior.&lt;/p&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(\boldsymbol{x} \in \R^m\)&lt;/span&gt; be a vector of &lt;em&gt;parameters&lt;/em&gt;.
The &lt;em&gt;forward model&lt;/em&gt; is denoted as &lt;span class="math"&gt;\(h: \R^m \to \R^n\)&lt;/span&gt;, and applying it to the &lt;span class="math"&gt;\(m\)&lt;/span&gt; parameters returns a vector of &lt;span class="math"&gt;\(n\)&lt;/span&gt; &lt;em&gt;responses&lt;/em&gt; &lt;span class="math"&gt;\(\boldsymbol{y} \in \R^n\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;observation&lt;/em&gt; vector &lt;span class="math"&gt;\(\boldsymbol{d} \in \R^n\)&lt;/span&gt; is equal to the response plus a random noise component &lt;span class="math"&gt;\(\boldsymbol{\epsilon}_d\)&lt;/span&gt;, which is often called the &lt;em&gt;observation noise&lt;/em&gt; or &lt;em&gt;measurement noise&lt;/em&gt;.
The full equation is then &lt;span class="math"&gt;\(\boldsymbol{d} = h(\boldsymbol{x}) + \boldsymbol{\epsilon}_d\)&lt;/span&gt;, where &lt;span class="math"&gt;\(\boldsymbol{\epsilon}_d \sim \mathcal{N}(\boldsymbol{0}, \Sigma_{\epsilon})\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Think for instance of a petroleum reservoir model.
The parameters &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt; are uncertain static properties of the subsurface, e.g., porosity.
The forward model &lt;span class="math"&gt;\(h\)&lt;/span&gt; is a reservoir flow simulator.
It solves the fluid flow equations over the grid and reports predicted production data &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt;, for instance oil production at a well.
A single evaluation of &lt;span class="math"&gt;\(h\)&lt;/span&gt; may take hours of compute, and we only have access to its inputs and outputs.
The match is never exact, so the real-world observation &lt;span class="math"&gt;\(\boldsymbol{d}\)&lt;/span&gt; departs from the simulator response by a noise term &lt;span class="math"&gt;\(\boldsymbol{\epsilon}_d\)&lt;/span&gt;.
The inverse problem, called &lt;em&gt;history matching&lt;/em&gt;, is: given the observed production &lt;span class="math"&gt;\(\boldsymbol{d}\)&lt;/span&gt;, how can we update our knowledge of the parameters &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt;?
Characterizing the posterior distribution of &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt; given &lt;span class="math"&gt;\(\boldsymbol{d}\)&lt;/span&gt; is the subject of the rest of these notes.&lt;/p&gt;
&lt;h3 id="exact-posterior-distribution-pboldsymbolx-mid-boldsymbold"&gt;Exact posterior distribution &lt;span class="math"&gt;\(p(\boldsymbol{x} \mid \boldsymbol{d})\)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;In this section we assume that the prior distribution over parameters &lt;span class="math"&gt;\(p(\boldsymbol{x})\)&lt;/span&gt; is multivariate Gaussian, and that the forward model &lt;span class="math"&gt;\(h\)&lt;/span&gt; is linear. Then &lt;span class="math"&gt;\(\boldsymbol{d} = H \boldsymbol{x} + \boldsymbol{\epsilon}_d = \boldsymbol{y} + \boldsymbol{\epsilon}_d\)&lt;/span&gt;, where &lt;span class="math"&gt;\(H \in \R^{n \times m}\)&lt;/span&gt; is a matrix.
The posterior distribution &lt;span class="math"&gt;\(p(\boldsymbol{x} \mid \boldsymbol{d})\)&lt;/span&gt; is also multivariate Gaussian.&lt;/p&gt;
&lt;p&gt;In words, we will answer “Suppose I have a prior belief over parameters &lt;span class="math"&gt;\(p(\boldsymbol{x})\)&lt;/span&gt; and I have observed an observation &lt;span class="math"&gt;\(\boldsymbol{d}\)&lt;/span&gt;—how can I update my prior to a posterior?”&lt;/p&gt;
&lt;div class="remark" id="remark-response-covariance" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Remark 1 (Response covariance).&lt;/strong&gt;
Recall from Petersen and Pedersen (2012, §6.2)&lt;sup id="fnref:petersen2012"&gt;&lt;a class="footnote-ref" href="#fn:petersen2012"&gt;18&lt;/a&gt;&lt;/sup&gt; that &lt;span class="math"&gt;\(\operatorname{Cov}\left[A\boldsymbol{x}, B\boldsymbol{y}\right] = A \operatorname{Cov}\left[\boldsymbol{x}, \boldsymbol{y}\right] B^T\)&lt;/span&gt; when &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; are random variables.
When both arguments are identical, we’ll use a shorthand notation: &lt;span class="math"&gt;\(\operatorname{Cov}\left[\boldsymbol{x}\right] := \operatorname{Cov}\left[\boldsymbol{x}, \boldsymbol{x}\right] = \operatorname{Var}\left[\boldsymbol{x}\right]\)&lt;/span&gt;.
Similarly, when there is no ambiguity we’ll simply write &lt;span class="math"&gt;\(\Sigma_x\)&lt;/span&gt; instead of &lt;span class="math"&gt;\(\Sigma_{xx}\)&lt;/span&gt; for the covariance of &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;In both equations below we’ll use the fact that &lt;span class="math"&gt;\(\boldsymbol{\epsilon}_d\)&lt;/span&gt; is independent of &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt;.
The covariance in &lt;span class="math"&gt;\(\boldsymbol{d}\)&lt;/span&gt; is the covariance of &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt;, propagated through the linear map &lt;span class="math"&gt;\(H\)&lt;/span&gt;, plus the covariance of the observation noise &lt;span class="math"&gt;\(\boldsymbol{\epsilon}_d\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{align*}
\Sigma_{d} &amp;amp;=
\operatorname{Cov}\left[ \boldsymbol{d} \right]
= \operatorname{Cov}\left[ H\boldsymbol{x} + \boldsymbol{\epsilon}_d \right] \\
&amp;amp;= \operatorname{Cov}\left[ H\boldsymbol{x} \right]
+ \operatorname{Cov}\left[ \boldsymbol{\epsilon}_d \right]
= H \operatorname{Cov}\left[\boldsymbol{x}, \boldsymbol{x}\right] H^T
+ \Sigma_{\epsilon} \\
&amp;amp;= H \Sigma_x H^T
+ \Sigma_{\epsilon}.
\end{align*}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;Similarly, the cross-covariance can be expressed as:&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{align*}
\Sigma_{xd} &amp;amp;=
\operatorname{Cov}\left[ \boldsymbol{x}, \boldsymbol{d} \right] =
\operatorname{Cov}\left[ \boldsymbol{x}, H\boldsymbol{x} + \boldsymbol{\epsilon}_d \right] \\
&amp;amp;=
\operatorname{Cov}\left[ \boldsymbol{x}, \boldsymbol{x} \right] H^T = \Sigma_x H^T
\end{align*}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;Note also that &lt;span class="math"&gt;\(\Sigma_{xy} = \operatorname{Cov}\left[ \boldsymbol{x}, \boldsymbol{y} \right] = \operatorname{Cov}\left[ \boldsymbol{x}, H\boldsymbol{x} \right] = \operatorname{Cov}\left[\boldsymbol{x}, \boldsymbol{x}\right] H^T = \Sigma_{xd}\)&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Suppose the prior over the parameters &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt; and the observation &lt;span class="math"&gt;\(\boldsymbol{d}\)&lt;/span&gt;, conditioned on the parameters, are given by multivariate Gaussians:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\label{eqn:model_def_gaussian} \tag{1}
p(\boldsymbol{x}) &amp;amp;= \mathcal{N}\left( \boldsymbol{x} \mid \boldsymbol{\mu}_x, \Sigma_x \right) \\
p(\boldsymbol{d} \mid \boldsymbol{x}) &amp;amp;= \mathcal{N}( \boldsymbol{d} \mid H\boldsymbol{x}, \Sigma_{\epsilon})
\end{align}&lt;/div&gt;
&lt;p&gt;Then the posterior distribution over parameters, conditioned on the response, is also a multivariate Gaussian &lt;span class="math"&gt;\(p(\boldsymbol{x} \mid \boldsymbol{d}) = \mathcal{N} (\boldsymbol{\mu}_{x \mid d}, \Sigma_{x \mid d})\)&lt;/span&gt;.
The posterior mean and covariance are:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\boldsymbol{\mu}_{x \mid d} &amp;amp;=
\Sigma_{x \mid d} \left(
H^T \Sigma_\epsilon^{-1} \boldsymbol{d} + \Sigma_x^{-1} \boldsymbol{\mu}_x
\right)
\label{eqn:posterior_mean_precision} \tag{2} \\
\Sigma_{x \mid d} &amp;amp;=
\left( \Sigma_x^{-1} + H^T \Sigma_\epsilon^{-1} H \right)^{-1}
\label{eqn:posterior_cov_precision} \tag{3}
\end{align}&lt;/div&gt;
&lt;p&gt;The equations above are well known, and can for instance be found in Bishop (2006, §2.3.3)&lt;sup id="fnref:bishop2006"&gt;&lt;a class="footnote-ref" href="#fn:bishop2006"&gt;19&lt;/a&gt;&lt;/sup&gt;.&lt;sup id="fnref:fn-logposterior"&gt;&lt;a class="footnote-ref" href="#fn:fn-logposterior"&gt;2&lt;/a&gt;&lt;/sup&gt;
In the equations above, the posterior mean &lt;span class="math"&gt;\(\boldsymbol{\mu}_{x \mid d}\)&lt;/span&gt; is expressed in terms of precision matrices &lt;span class="math"&gt;\(\Sigma_x^{-1}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\Sigma_\epsilon^{-1}\)&lt;/span&gt; rather than covariance matrices &lt;span class="math"&gt;\(\Sigma_x\)&lt;/span&gt; and &lt;span class="math"&gt;\(\Sigma_\epsilon\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="remark" id="remark-posterior-covariance" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Remark 2 (Posterior covariance).&lt;/strong&gt;
We show the Schur complement form of the posterior covariance &lt;span class="math"&gt;\(\Sigma_{x \mid d}\)&lt;/span&gt;.
Starting with equation &lt;span class="math"&gt;\(\eqref{eqn:posterior_cov_precision}\)&lt;/span&gt;, we first use the Woodbury identity, then the results from &lt;a href="#remark-response-covariance"&gt;Remark 1&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{align*}
\Sigma_{x \mid d} &amp;amp;=
\left( \Sigma_x^{-1} + H^T \Sigma_\epsilon^{-1} H \right)^{-1} \\
&amp;amp;= \Sigma_x - \Sigma_x H^T
\left(
\Sigma_\epsilon + H \Sigma_x H^T
\right)^{-1}
H \Sigma_x \\
&amp;amp;= \Sigma_x - \Sigma_{xd}
\Sigma_d^{-1}
\Sigma_{dx}
\end{align*}&lt;/div&gt;
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The Kalman gain form is an alternative way to express the update equations above, and we’ll spend the rest of this section deriving it.
It’s more commonly used in the Kalman filter literature, and the update is given by:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\boldsymbol{\mu}_{x \mid d} &amp;amp;= \boldsymbol{\mu}_{x} + K (\boldsymbol{d} - H \boldsymbol{\mu}_{x})
\label{eqn:posterior_mean_kalman} \tag{4} \\
K &amp;amp;:= \Sigma_x H^T
\left(
\Sigma_\epsilon + H \Sigma_x H^T
\right)^{-1} = \Sigma_{xd} \Sigma_d^{-1}
\label{eqn:kalman_gain} \tag{5}
\end{align}&lt;/div&gt;
&lt;p&gt;In the equation above, &lt;span class="math"&gt;\(H \boldsymbol{\mu}_{x}\)&lt;/span&gt; is the predicted mean, &lt;span class="math"&gt;\((\boldsymbol{d} - H \boldsymbol{\mu}_{x})\)&lt;/span&gt; is the &lt;em&gt;innovation&lt;/em&gt; term and &lt;span class="math"&gt;\(K\)&lt;/span&gt; is the &lt;em&gt;Kalman gain&lt;/em&gt;.
In words: the posterior equals the prior, plus a linear combination of the innovation (residuals).&lt;/p&gt;
&lt;p&gt;We will now derive the Kalman update in equation &lt;span class="math"&gt;\(\eqref{eqn:posterior_mean_kalman}\)&lt;/span&gt;.
The first step is to show that &lt;span class="math"&gt;\(K = \Sigma_{x \mid d}H^T \Sigma_{\epsilon}^{-1}\)&lt;/span&gt;.
To see this, we start by assuming that both expression are equal:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\label{eqn:push_through_kalman} \tag{6}
\Sigma_{x \mid d}H^T \Sigma_{\epsilon}^{-1} =
K =
\Sigma_x H^T
\left(
\Sigma_\epsilon + H \Sigma_x H^T
\right)^{-1}
\end{equation}&lt;/div&gt;
&lt;p&gt;We right-multiply both sides with &lt;span class="math"&gt;\(\left( \Sigma_\epsilon + H \Sigma_x H^T \right)\)&lt;/span&gt;, then left-multiply both sides with &lt;span class="math"&gt;\(\Sigma_{x \mid d}^{-1}\)&lt;/span&gt; and finally expand both sides to show that they both equal &lt;span class="math"&gt;\(H^T + H^T \Sigma_\epsilon^{-1} H \Sigma_x H^T\)&lt;/span&gt;.&lt;sup id="fnref:fn-pushthrough"&gt;&lt;a class="footnote-ref" href="#fn:fn-pushthrough"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The second step is to realize that, using &lt;a href="#remark-posterior-covariance"&gt;Remark 2&lt;/a&gt;, we can write&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\Sigma_{x \mid d}\Sigma_{x}^{-1} &amp;amp;=
\Sigma_{x \mid d}
\left( \Sigma_{x \mid d}^{-1} - H^T \Sigma_{\epsilon}^{-1} H \right) \\
&amp;amp;= I - \Sigma_{x \mid d} H^T \Sigma_{\epsilon}^{-1} H = I - KH.
\end{align*}&lt;/div&gt;
&lt;p&gt;With these two results in hand, we start with equation &lt;span class="math"&gt;\(\eqref{eqn:posterior_mean_precision}\)&lt;/span&gt; and show that it can indeed be transformed into equation &lt;span class="math"&gt;\(\eqref{eqn:posterior_mean_kalman}\)&lt;/span&gt;.
The update equation for the mean becomes&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{\mu}_{x \mid d} &amp;amp;=
\Sigma_{x \mid d} \left(
H^T \Sigma_\epsilon^{-1} \boldsymbol{d} + \Sigma_x^{-1} \boldsymbol{\mu}_x
\right) \\
&amp;amp;=
\Sigma_{x \mid d}
H^T \Sigma_\epsilon^{-1} \boldsymbol{d} + \Sigma_{x \mid d} \Sigma_x^{-1} \boldsymbol{\mu}_x
\\
&amp;amp;= K \boldsymbol{d} + (I - KH) \boldsymbol{\mu}_x = \boldsymbol{\mu}_x + K (\boldsymbol{d} - H\boldsymbol{\mu}_x )
\end{align*}&lt;/div&gt;
&lt;p&gt;In summary then, the Kalman-style update equations are:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{\mu}_{x \mid d}
&amp;amp;= \boldsymbol{\mu}_x + K (\boldsymbol{d} - H\boldsymbol{\mu}_x )
\\
\Sigma_{x \mid d} &amp;amp;= (I - KH) \Sigma_x
\end{align*}&lt;/div&gt;
&lt;p&gt;We have seen that there are different ways to express the update equations: the precision form of equation &lt;span class="math"&gt;\(\eqref{eqn:posterior_cov_precision}\)&lt;/span&gt;, the Schur form of &lt;a href="#remark-posterior-covariance"&gt;Remark 2&lt;/a&gt; and the Kalman form in equation &lt;span class="math"&gt;\(\eqref{eqn:posterior_mean_kalman}\)&lt;/span&gt;.
Which one should we use?
It depends on what we wish to show or prove, or how we want to structure our computations.
For instance, the Ensemble Smoother—which we discuss next—uses a Kalman style update with covariances.&lt;/p&gt;
&lt;h2 id="the-ensemble-smoother"&gt;The Ensemble Smoother&lt;/h2&gt;
&lt;p&gt;The Ensemble Smoother (&lt;span class="caps"&gt;ES&lt;/span&gt;) describes the posterior with an &lt;em&gt;ensemble&lt;/em&gt; of &lt;em&gt;realizations&lt;/em&gt; (i.e., a set of samples) instead of &lt;span class="math"&gt;\(\boldsymbol{\mu}_{x \mid d}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\Sigma_{x \mid d}\)&lt;/span&gt;.
First we draw &lt;span class="math"&gt;\(r\)&lt;/span&gt; realizations &lt;span class="math"&gt;\(\{\boldsymbol{x}_j\}_{j=1}^{r}\)&lt;/span&gt; from the prior distribution &lt;span class="math"&gt;\(p(\boldsymbol{x})\)&lt;/span&gt;.
Then we propagate them through the forward model &lt;span class="math"&gt;\(h\)&lt;/span&gt; and obtain &lt;span class="math"&gt;\(\boldsymbol{y}_j = h(\boldsymbol{x}_j)\)&lt;/span&gt; for each realization &lt;span class="math"&gt;\(j=1, \ldots, r\)&lt;/span&gt;.
Finally, we perform &lt;em&gt;data assimilation&lt;/em&gt;; a Kalman-style update that transforms the prior ensemble to the posterior ensemble by conditioning on the observation.&lt;/p&gt;
&lt;p&gt;The upside of &lt;span class="caps"&gt;ES&lt;/span&gt; is that we don’t need knowledge of the forward model &lt;span class="math"&gt;\(h\)&lt;/span&gt; to compute a posterior; &lt;span class="math"&gt;\(h\)&lt;/span&gt; can be a black-box function.
The downside is that the update equation is derived assuming (1) a linear &lt;span class="math"&gt;\(h\)&lt;/span&gt;, (2) Gaussian distributions and (3) infinitely many realizations.
&lt;span class="caps"&gt;ES&lt;/span&gt; won’t actually sample the true, analytical posterior except in the happy case when conditions (1), (2) and (3) are all satisfied.
Instead we approximate it.&lt;sup id="fnref:fn-approx"&gt;&lt;a class="footnote-ref" href="#fn:fn-approx"&gt;4&lt;/a&gt;&lt;/sup&gt;
Since the forward model is expensive to compute, the number of realizations &lt;span class="math"&gt;\(r\)&lt;/span&gt; is much smaller than the number of parameters &lt;span class="math"&gt;\(m\)&lt;/span&gt; and responses &lt;span class="math"&gt;\(n\)&lt;/span&gt;.
This leads to poor estimates of covariances due to spurious correlations.&lt;/p&gt;
&lt;p&gt;We will now derive the update equation for &lt;span class="caps"&gt;ES&lt;/span&gt;, which computes the posterior realizations given the prior realizations.
First, some notation: let &lt;span class="math"&gt;\(X \in \R^{m \times r}\)&lt;/span&gt; be a matrix with &lt;span class="math"&gt;\(r\)&lt;/span&gt; realizations (ensemble members); one per column.&lt;sup id="fnref:fn-transpose"&gt;&lt;a class="footnote-ref" href="#fn:fn-transpose"&gt;5&lt;/a&gt;&lt;/sup&gt;
Let &lt;span class="math"&gt;\(Y = h(X) = [ h(\boldsymbol{x}_1) \mid h(\boldsymbol{x}_2) \mid \cdots \mid h(\boldsymbol{x}_r)] \in \R^{n \times r}\)&lt;/span&gt; be a matrix of responses, where &lt;span class="math"&gt;\(h\)&lt;/span&gt; is applied to each column.&lt;/p&gt;
&lt;div class="statement" id="statement-es" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Statement 1.&lt;/strong&gt;
The matrix form of the &lt;span class="caps"&gt;ES&lt;/span&gt; update equation, which updates every realization (column) in &lt;span class="math"&gt;\(X\)&lt;/span&gt;, is&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{equation}
\label{eqn:ensemble_smoother_update} \tag{7}
X_{\text{posterior}}
= X_{\text{prior}} +
\hat{\Sigma}_{xy}
(
\Sigma_\epsilon + \hat{\Sigma}_y
)^{-1} (\boldsymbol{d} \boldsymbol{1}^T - (Y + E))
\end{equation}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;where each column of &lt;span class="math"&gt;\(E\)&lt;/span&gt; is independently sampled observation &lt;span class="math"&gt;\(\boldsymbol{\epsilon}_d\)&lt;/span&gt;.
In other words, &lt;span class="math"&gt;\(E_{\cdot, j} \sim \mathcal{N}(\boldsymbol{0}, \Sigma_\epsilon)\)&lt;/span&gt; for every column &lt;span class="math"&gt;\(j\)&lt;/span&gt;.
The vector &lt;span class="math"&gt;\(\boldsymbol{1}\)&lt;/span&gt; is a vector of &lt;span class="math"&gt;\(r\)&lt;/span&gt; ones, so &lt;span class="math"&gt;\(\boldsymbol{d} \boldsymbol{1}^T\)&lt;/span&gt; is a matrix with the observation &lt;span class="math"&gt;\(\boldsymbol{d}\)&lt;/span&gt; repeated in each of the &lt;span class="math"&gt;\(r\)&lt;/span&gt; columns.
The matrices &lt;span class="math"&gt;\(\hat{\Sigma}_{xy}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\hat{\Sigma}_y\)&lt;/span&gt; are estimates of the cross-covariance and the covariance.
The most common estimate for, e.g., &lt;span class="math"&gt;\(\hat{\Sigma}_{xy}\)&lt;/span&gt; is the empirical cross covariance matrix &lt;span class="math"&gt;\(\bar{X}\bar{Y}^T / (r-1) \in \R^{m \times n}\)&lt;/span&gt;, where &lt;span class="math"&gt;\(\bar{X}\)&lt;/span&gt; is a centered &lt;span class="math"&gt;\(X\)&lt;/span&gt; in which every parameter (row) has its mean subtracted.&lt;sup id="fnref:fn-centering"&gt;&lt;a class="footnote-ref" href="#fn:fn-centering"&gt;6&lt;/a&gt;&lt;/sup&gt;
&lt;span class="math"&gt;\(\bar{Y}\)&lt;/span&gt; is a centered &lt;span class="math"&gt;\(Y\)&lt;/span&gt; and &lt;span class="math"&gt;\(\hat{\Sigma}_{y}\)&lt;/span&gt; is computed similarly.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We’ll spend the rest of this section showing that equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt; is the correct update equation in the following sense: when we use it on a linear model and the number of realizations &lt;span class="math"&gt;\(r\)&lt;/span&gt; goes to infinity, the posterior mean and posterior covariance matches the analytical posterior.&lt;/p&gt;
&lt;p&gt;To derive equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt;, we start by examining the Gaussian update equation for the mean:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{\mu}_{x \mid d}
&amp;amp;= \boldsymbol{\mu}_x + K (\boldsymbol{d} - H\boldsymbol{\mu}_x ) \\
&amp;amp;= \boldsymbol{\mu}_x + \Sigma_x H^T
\left(
\Sigma_\epsilon + H \Sigma_x H^T
\right)^{-1} (\boldsymbol{d} - H\boldsymbol{\mu}_x )
\end{align*}&lt;/div&gt;
&lt;p&gt;We use the results from &lt;a href="#remark-response-covariance"&gt;Remark 1&lt;/a&gt; and substitute
&lt;span class="math"&gt;\(\Sigma_x H^T = \Sigma_{xy}\)&lt;/span&gt;
and
&lt;span class="math"&gt;\(H \Sigma_x H^T = \operatorname{Cov}\left[ H\boldsymbol{x} \right] = \operatorname{Cov}\left[ \boldsymbol{y} \right] = \Sigma_y\)&lt;/span&gt;.
This gives us Kalman gain in the form &lt;span class="math"&gt;\(K = \Sigma_{xy} \left( \Sigma_\epsilon + \Sigma_y \right)^{-1}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The update equation for each realization &lt;span class="math"&gt;\(j\)&lt;/span&gt;, based on equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt;, is given by&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\boldsymbol{x}^{j}_{\text{posterior}}
=
\boldsymbol{x}^{j}_{\text{prior}} + K(\boldsymbol{d} - (\boldsymbol{y}^j + \boldsymbol{\epsilon}_d^j)),
\end{equation*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(\boldsymbol{\epsilon}_d^j \sim \mathcal{N}(\boldsymbol{0}, \Sigma_\epsilon)\)&lt;/span&gt;.
To see why this is the correct update and why we have to perturb the response &lt;span class="math"&gt;\(\boldsymbol{y}^j\)&lt;/span&gt; with observation noise &lt;span class="math"&gt;\(\boldsymbol{\epsilon}_d^j\)&lt;/span&gt;, we have to prove that in the Gauss-linear case, this update equation recovers the same posterior mean and posterior covariance as the analytical equations in &lt;span class="math"&gt;\(\eqref{eqn:posterior_mean_precision}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\eqref{eqn:posterior_cov_precision}\)&lt;/span&gt;.
To this end, suppose that &lt;span class="math"&gt;\(h\)&lt;/span&gt; is linear, so &lt;span class="math"&gt;\(\boldsymbol{y}^j = H \boldsymbol{x}^{j}_{\text{prior}}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;We update each realization with the equation&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{x}_{\text{posterior}}
&amp;amp;=
\boldsymbol{x}_{\text{prior}} + K(\boldsymbol{d} - (H \boldsymbol{x}_{\text{prior}} + \boldsymbol{\epsilon}_d)) \\
\boldsymbol{x}_{\text{prior}} &amp;amp;\sim \mathcal{N}(\boldsymbol{\mu}_x, \Sigma_x) \\
\boldsymbol{\epsilon}_d &amp;amp;\sim \mathcal{N}(\boldsymbol{0}, \Sigma_\epsilon),
\end{align*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(\boldsymbol{d}\)&lt;/span&gt; is no longer a random variable, but an actual real-world observation.
The residual term &lt;span class="math"&gt;\((\boldsymbol{d} - (H \boldsymbol{x}_{\text{prior}} + \boldsymbol{\epsilon}_d))\)&lt;/span&gt; is the actual value minus the predicted value.&lt;/p&gt;
&lt;p&gt;First we show that the posterior mean equals equation &lt;span class="math"&gt;\(\eqref{eqn:posterior_mean_kalman}\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\mathbb{E} \left[ \boldsymbol{x}_{\text{posterior}} \right]
&amp;amp;= \mathbb{E} \left[ \boldsymbol{x}_{\text{prior}} +
K(\boldsymbol{d} - (H \boldsymbol{x}_{\text{prior}} + \boldsymbol{\epsilon}_d)) \right] \\
&amp;amp;= \mathbb{E} \left[ \boldsymbol{x}_{\text{prior}} \right]
+
K
\left(
\mathbb{E} \left[ \boldsymbol{d} \right]
-
\mathbb{E} \left[ H \boldsymbol{x}_{\text{prior}} \right]
-
\mathbb{E} \left[ \boldsymbol{\epsilon}_d \right]
\right) \\
&amp;amp;=
\boldsymbol{\mu}_x + K \left(
\boldsymbol{d}
-
H \mathbb{E} \left[ \boldsymbol{x}_{\text{prior}} \right]
-
\boldsymbol{0}
\right) \\
&amp;amp;=
\boldsymbol{\mu}_x + K(\boldsymbol{d} - H \boldsymbol{\mu}_x )
\end{align*}&lt;/div&gt;
&lt;p&gt;Second we show that the posterior covariance equals equation &lt;span class="math"&gt;\(\eqref{eqn:posterior_cov_precision}\)&lt;/span&gt;.
More specifically, we show it to be equal to the Schur complement form derived in &lt;a href="#remark-posterior-covariance"&gt;Remark 2&lt;/a&gt;.
Below we use the fact that &lt;span class="math"&gt;\(K\boldsymbol{d}\)&lt;/span&gt; is constant and that &lt;span class="math"&gt;\(\operatorname{Cov} \left[ \boldsymbol{x}_{\text{prior}}, \boldsymbol{\epsilon}_d \right] = 0\)&lt;/span&gt; when we distribute the covariance over the terms.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\operatorname{Cov} \left[ \boldsymbol{x}_{\text{posterior}} \right]
&amp;amp;= \operatorname{Cov} \left[ \boldsymbol{x}_{\text{prior}} +
K(
\boldsymbol{d} - (H \boldsymbol{x}_{\text{prior}} + \boldsymbol{\epsilon}_d)
) \right] \\
&amp;amp;= \operatorname{Cov} \left[
(I - KH) \boldsymbol{x}_{\text{prior}}
+ K \boldsymbol{d}
- K \boldsymbol{\epsilon}_d
\right] \\
&amp;amp;= \operatorname{Cov} \left[
(I - KH) \boldsymbol{x}_{\text{prior}}
\right] +
\operatorname{Cov} \left[
-K \boldsymbol{\epsilon}_d
\right]
\\
&amp;amp;= (I - KH) \Sigma_{x} (I - KH)^T + K \Sigma_{\epsilon} K^T \\
&amp;amp;= \Sigma_{x} - \Sigma_{x} H^T K^T - K H \Sigma_{x} + K H \Sigma_{x} H^T K^T + K \Sigma_{\epsilon} K^T \\
&amp;amp;= \Sigma_{x} -
\underbrace{\Sigma_{x} H^T}_{\Sigma_{xd}} K^T -
K \underbrace{H \Sigma_{x}}_{\Sigma_{dx}} +
K \underbrace{\left( H \Sigma_{x} H^T + \Sigma_{\epsilon} \right) }_{\Sigma_{d}} K^T \\
&amp;amp;= \Sigma_{x} - \Sigma_{xd} K^T - K \Sigma_{dx} +
K \Sigma_{d} K^T
\end{align*}&lt;/div&gt;
&lt;p&gt;The equalities shown in underbraces were all derived in &lt;a href="#remark-response-covariance"&gt;Remark 1&lt;/a&gt;.
From equation &lt;span class="math"&gt;\(\eqref{eqn:kalman_gain}\)&lt;/span&gt;, we observe that &lt;span class="math"&gt;\(K \Sigma_{d} = (\Sigma_{xd} \Sigma_{d}^{-1}) \Sigma_{d} = \Sigma_{xd}\)&lt;/span&gt;.
The last term, &lt;span class="math"&gt;\(K \Sigma_{d} K^T\)&lt;/span&gt;, can therefore be re-written as &lt;span class="math"&gt;\(\Sigma_{xd} K^T\)&lt;/span&gt; and cancels the second term.
We are left with&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\operatorname{Cov} \left[ \boldsymbol{x}_{\text{posterior}} \right]
= \Sigma_{x} - K \Sigma_{dx} = \Sigma_{x} - \Sigma_{xd} \Sigma_{d}^{-1} \Sigma_{dx}
= \Sigma_{x \mid d},
\end{equation*}&lt;/div&gt;
&lt;p&gt;where the last equality follows from &lt;a href="#remark-posterior-covariance"&gt;Remark 2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In summary, we have shown that if we use equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt; to update the ensemble, then the resulting posterior mean and covariance over the realizations matches the analytical posterior in the Gauss-linear case.&lt;/p&gt;
&lt;h3 id="computation"&gt;Computation&lt;/h3&gt;
&lt;p&gt;To implement &lt;span class="caps"&gt;ES&lt;/span&gt; we must compute equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt; given in &lt;a href="#statement-es"&gt;Statement 1&lt;/a&gt;.
Substituting empirical population estimates for &lt;span class="math"&gt;\(\hat{\Sigma}_{xy}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\hat{\Sigma}_y\)&lt;/span&gt;, the update equation becomes&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
X_{\text{posterior}}
= X_{\text{prior}} +
\frac{\bar{X}\bar{Y}^T}{r-1}
\left(
\Sigma_\epsilon + \frac{\bar{Y}\bar{Y}^T}{r-1}
\right)^{-1} \left( \boldsymbol{d} \boldsymbol{1}^T - (Y + E) \right).
\end{equation*}&lt;/div&gt;
&lt;p&gt;This equation should never be implemented naïvely.
For instance, the inverse should not be explicitly formed and neither should the huge cross-covariance matrix &lt;span class="math"&gt;\(\bar{X}\bar{Y}^T\)&lt;/span&gt; (order of matrix multiplication matters).&lt;/p&gt;
&lt;p&gt;Emerick and Reynolds (2012, Appendix A)&lt;sup id="fnref:emerick2012"&gt;&lt;a class="footnote-ref" href="#fn:emerick2012"&gt;20&lt;/a&gt;&lt;/sup&gt; write about computation, and explain two tricks: (1) scaling the system of equations and (2) subspace inversion.
Emerick (2016, Appendix B)&lt;sup id="fnref:emerick2016"&gt;&lt;a class="footnote-ref" href="#fn:emerick2016"&gt;21&lt;/a&gt;&lt;/sup&gt; also discusses computation.
But their sentence “it is computationally more efficient to rescale based only on the diagonal elements” is not as clear as it could be: using only the diagonal elements means we don’t solve the original equation.&lt;/p&gt;
&lt;p&gt;The best way I know to solve this system is to scale with the Cholesky factor of &lt;span class="math"&gt;\(\Sigma_{\epsilon}\)&lt;/span&gt;, then do a subspace inversion with a single &lt;span class="caps"&gt;SVD&lt;/span&gt;.
This is different from both papers above, and is documented &lt;a href="https://github.com/equinor/iterative_ensemble_smoother/blob/140522402eb13ff3df5ec51027c8faca974252ae/src/iterative_ensemble_smoother/esmda_inversion.py"&gt;iterative_ensemble_smoother&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-ensemble-information-filter"&gt;The Ensemble Information Filter&lt;/h2&gt;
&lt;p&gt;One issue with &lt;span class="caps"&gt;ES&lt;/span&gt; is that in equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt; we have to estimate &lt;span class="math"&gt;\(\Sigma_{xy}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\Sigma_{y}\)&lt;/span&gt;. When &lt;span class="math"&gt;\(m\)&lt;/span&gt; and &lt;span class="math"&gt;\(n\)&lt;/span&gt; are larger than &lt;span class="math"&gt;\(r\)&lt;/span&gt;, empirical covariance is a poor estimator that picks up spurious correlations—the update ends up being dominated by noise.&lt;/p&gt;
&lt;p&gt;What if we avoid covariances and instead work with precision matrices and &lt;span class="math"&gt;\(H\)&lt;/span&gt;?
After all, there is a substantial literature on high-dimensional regression that we can lean on to estimate &lt;span class="math"&gt;\(H\)&lt;/span&gt;.
Also, parameters with conditional independence structure are often better described by precision matrices &lt;span class="math"&gt;\(\Lambda_x\)&lt;/span&gt; rather than covariance matrices &lt;span class="math"&gt;\(\Sigma_{x}\)&lt;/span&gt;, see Rue and Held (2005, Ch. 2)&lt;sup id="fnref:rue2005"&gt;&lt;a class="footnote-ref" href="#fn:rue2005"&gt;22&lt;/a&gt;&lt;/sup&gt;.
The following ideas are due to Lunde (2025)&lt;sup id="fnref2:lunde2025"&gt;&lt;a class="footnote-ref" href="#fn:lunde2025"&gt;17&lt;/a&gt;&lt;/sup&gt;:
(1) estimate &lt;span class="math"&gt;\(H\)&lt;/span&gt; with sparse high dimensional regression and
(2) think in terms of precision rather than covariance.
We’ll refer to his work as the Ensemble Information Filter (EnIF).
The &lt;em&gt;information&lt;/em&gt; terminology means that we parametrize in terms of precision rather than covariance.
We’re discussing smoothers, so the &lt;em&gt;filter&lt;/em&gt; does not exactly match this document—but we’ll stick with EnIF rather than inventing a new abbreviation.&lt;/p&gt;
&lt;p&gt;Let us approximate &lt;span class="math"&gt;\(\boldsymbol{y} = h(\boldsymbol{x})\)&lt;/span&gt; as &lt;span class="math"&gt;\(\boldsymbol{y} = H \boldsymbol{x} + \boldsymbol{r}\)&lt;/span&gt;. Here &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt; is the residual (the non-linear part of &lt;span class="math"&gt;\(h\)&lt;/span&gt;) and &lt;span class="math"&gt;\(H\)&lt;/span&gt; is a sparse matrix.&lt;sup id="fnref:fn-bias"&gt;&lt;a class="footnote-ref" href="#fn:fn-bias"&gt;7&lt;/a&gt;&lt;/sup&gt;
If we model &lt;span class="math"&gt;\(\boldsymbol{r} \sim \mathcal{N}(\boldsymbol{0}, \Sigma_{r})\)&lt;/span&gt;, then a large &lt;span class="math"&gt;\(\Sigma_{r}\)&lt;/span&gt; means the linear model is poor.&lt;sup id="fnref:fn-residual"&gt;&lt;a class="footnote-ref" href="#fn:fn-residual"&gt;8&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class="statement" id="statement-enif" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Statement 2.&lt;/strong&gt;
The matrix form of the EnIF update equation, which updates every realization (column) in &lt;span class="math"&gt;\(X\)&lt;/span&gt;, is&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{align}
X_{\text{posterior}}
&amp;amp;= X_{\text{prior}} +
( \Lambda_x + H^T \tilde{\Lambda} H )^{-1} H^T \tilde{\Lambda}
(\boldsymbol{d} \boldsymbol{1}^T - (Y + E))
\label{eqn:ensemble_smoother_update_inf} \tag{8} \\
\tilde{\Lambda} &amp;amp;= (\Sigma_{d \mid x})^{-1} =
\left( \Sigma_{\epsilon} + \Sigma_{r} \right)^{-1}
\label{eqn:ensemble_smoother_update_precision_def} \tag{9}
\end{align}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(Y = h(X_{\text{prior}})\)&lt;/span&gt; applied column-wise and each column of &lt;span class="math"&gt;\(E\)&lt;/span&gt; is independently sampled observation &lt;span class="math"&gt;\(\boldsymbol{\epsilon}_d\)&lt;/span&gt;.
In other words, &lt;span class="math"&gt;\(E_{\cdot, j} \sim \mathcal{N}(\boldsymbol{0}, \Sigma_\epsilon)\)&lt;/span&gt; for every column &lt;span class="math"&gt;\(j\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="math"&gt;\(H\)&lt;/span&gt;, &lt;span class="math"&gt;\(\Lambda_x\)&lt;/span&gt; and &lt;span class="math"&gt;\(\Sigma_{r}\)&lt;/span&gt; must be estimated.
The forward map &lt;span class="math"&gt;\(H\)&lt;/span&gt; is estimated by regressing &lt;span class="math"&gt;\(Y = h(X_{\text{prior}})\)&lt;/span&gt; on &lt;span class="math"&gt;\(X_{\text{prior}}\)&lt;/span&gt;, typically with a sparse high dimensional method.
The precision &lt;span class="math"&gt;\(\Lambda_x\)&lt;/span&gt; must be estimated, taking into account the parameter ensemble &lt;span class="math"&gt;\(X\)&lt;/span&gt; as well as neighborhood (graph) information (sparsity pattern in &lt;span class="math"&gt;\(\Lambda_x\)&lt;/span&gt;).
The residual covariance &lt;span class="math"&gt;\(\Sigma_{r}\)&lt;/span&gt; can be estimated as &lt;span class="math"&gt;\(\operatorname{diag}\left( \operatorname{Cov}\left[ Y - HX \right] \right)\)&lt;/span&gt;, where &lt;span class="math"&gt;\(H\)&lt;/span&gt; is the estimated forward map.
Correcting for degrees of freedom is an option when estimating &lt;span class="math"&gt;\(\Sigma_{r}\)&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We will now sketch why equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update_inf}\)&lt;/span&gt; is the correct update equation, in the sense that posterior mean and covariance matches under the Gauss-linear model.
The analytical model given the assumption &lt;span class="math"&gt;\(\boldsymbol{y} = H \boldsymbol{x} + \boldsymbol{r}\)&lt;/span&gt; becomes:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
p(\boldsymbol{x}) &amp;amp;= \mathcal{N}\left( \boldsymbol{x} \mid \boldsymbol{\mu}_x, \Sigma_x \right) \\
p(\boldsymbol{d} \mid \boldsymbol{x}) &amp;amp;= \mathcal{N}( \boldsymbol{d} \mid H\boldsymbol{x}, \Sigma_{\epsilon} + \Sigma_{r})
\end{align*}&lt;/div&gt;
&lt;p&gt;This is analogous to equation &lt;span class="math"&gt;\(\eqref{eqn:model_def_gaussian}\)&lt;/span&gt;, but with &lt;span class="math"&gt;\(\Sigma_{\epsilon}\)&lt;/span&gt; replaced by &lt;span class="math"&gt;\(\Sigma_{\epsilon} + \Sigma_{r}\)&lt;/span&gt;.
The posterior distribution &lt;span class="math"&gt;\(p(\boldsymbol{x} \mid \boldsymbol{d}) = \mathcal{N} (\boldsymbol{\mu}_{x \mid d}, \Sigma_{x \mid d})\)&lt;/span&gt; over parameters is given by&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\boldsymbol{\mu}_{x \mid d} &amp;amp;=
\Sigma_{x \mid d} \left(
H^T \left( \Sigma_{\epsilon} + \Sigma_{r} \right)^{-1} \boldsymbol{d} + \Sigma_x^{-1} \boldsymbol{\mu}_x
\right)
\label{eqn:posterior_mean_precision_inf} \tag{10} \\
\Sigma_{x \mid d} &amp;amp;=
\left( \Sigma_x^{-1} + H^T \left( \Sigma_{\epsilon} + \Sigma_{r} \right)^{-1} H \right)^{-1}
\label{eqn:posterior_cov_precision_inf} \tag{11}
\end{align}&lt;/div&gt;
&lt;p&gt;From equation &lt;span class="math"&gt;\(\eqref{eqn:push_through_kalman}\)&lt;/span&gt; and the result in &lt;a href="#remark-response-covariance"&gt;Remark 1&lt;/a&gt;, we observe that we can write the Kalman gain as a function of precision matrices and &lt;span class="math"&gt;\(H\)&lt;/span&gt; as&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
K
= \Sigma_{x \mid d} H^T \Sigma_{d \mid x}^{-1}
= ( \Lambda_x + H^T \tilde{\Lambda} H )^{-1} H^T \tilde{\Lambda}
\end{equation*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(\tilde{\Lambda}\)&lt;/span&gt; is defined as in equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update_precision_def}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\Lambda_x = \Sigma_{x}^{-1}\)&lt;/span&gt; by definition.
At this point it’s quite easy to mimic the proof for the &lt;span class="caps"&gt;ES&lt;/span&gt; update equation, to show that equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update_inf}\)&lt;/span&gt; also is correct, in the sense that&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\mathbb{E} \left[ \boldsymbol{x}_{\text{posterior}} \right] = \boldsymbol{\mu}_{x \mid d}
\qquad
\operatorname{Cov} \left[ \boldsymbol{x}_{\text{posterior}} \right] = \Sigma_{x \mid d}
\end{equation*}&lt;/div&gt;
&lt;p&gt;The two differences between EnIF and &lt;span class="caps"&gt;ES&lt;/span&gt;, under the Gauss-linear assumption, are that (1) the Kalman gain relies on precision and &lt;span class="math"&gt;\(H\)&lt;/span&gt; instead of covariance and (2) the covariance of &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt; is accounted for.
We have no theoretical guarantees that EnIF is better than &lt;span class="caps"&gt;ES&lt;/span&gt;, in the sense that the update equation is equivalent but written in a different form.
The hope is that if we are able to compute high-quality estimates of &lt;span class="math"&gt;\(H\)&lt;/span&gt; and &lt;span class="math"&gt;\(\Lambda_x\)&lt;/span&gt;, the resulting update will also have high quality.&lt;/p&gt;
&lt;h3 id="computation_1"&gt;Computation&lt;/h3&gt;
&lt;p&gt;Here are some notes on computation of the EnIF update.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Subproblem 1 (Estimate &lt;span class="math"&gt;\(\Lambda_x\)&lt;/span&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the realizations in &lt;span class="math"&gt;\(X_\text{prior}\)&lt;/span&gt; are known to be multivariate Gaussian, e.g. &lt;span class="math"&gt;\(\boldsymbol{x}_\text{prior} \sim \mathcal{N}\left( \boldsymbol{x} \mid \boldsymbol{\mu}_x, \Sigma_x \right)\)&lt;/span&gt; with a known &lt;span class="math"&gt;\(\Sigma_x\)&lt;/span&gt;, then &lt;span class="math"&gt;\(\Lambda_x = \Sigma_x^{-1}\)&lt;/span&gt; and no estimation is needed.&lt;/li&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(X_\text{prior}\)&lt;/span&gt; is given without information about its distribution, but the adjacency matrix &lt;span class="math"&gt;\(G\)&lt;/span&gt; is given (&lt;span class="math"&gt;\(G_{ij} = 1\)&lt;/span&gt; iff &lt;span class="math"&gt;\((\Lambda_x)_{ij} \neq 0\)&lt;/span&gt;), then we have to solve the problem “estimate &lt;span class="math"&gt;\(\Lambda_x\)&lt;/span&gt;, given &lt;span class="math"&gt;\(X_\text{prior}\)&lt;/span&gt; and &lt;span class="math"&gt;\(G\)&lt;/span&gt;.” This can for instance be done with the algorithm given in Hastie et al. (2009, §17.3.1)&lt;sup id="fnref2:hastie2009"&gt;&lt;a class="footnote-ref" href="#fn:hastie2009"&gt;16&lt;/a&gt;&lt;/sup&gt; or the triangular transport approach given in Lunde (2025)&lt;sup id="fnref4:lunde2025"&gt;&lt;a class="footnote-ref" href="#fn:lunde2025"&gt;17&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(X_\text{prior}\)&lt;/span&gt; is given but no information about the graph structure &lt;span class="math"&gt;\(G\)&lt;/span&gt; is given, then the graph structure can be estimated. A good starting point for information about this problem is Hastie et al. (2009, §17.3.2)&lt;sup id="fnref3:hastie2009"&gt;&lt;a class="footnote-ref" href="#fn:hastie2009"&gt;16&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Subproblem 2 (Estimate &lt;span class="math"&gt;\(H\)&lt;/span&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The problem is to find &lt;span class="math"&gt;\(H \in \mathbb{R}^{n \times m}\)&lt;/span&gt; such that &lt;span class="math"&gt;\(Y \approx H X\)&lt;/span&gt;.
This can be formulated as &lt;span class="math"&gt;\(n\)&lt;/span&gt; regressions, one for each response: &lt;span class="math"&gt;\(X^T H^T \approx Y^T\)&lt;/span&gt; in matrix notation or &lt;span class="math"&gt;\(X^T \boldsymbol{h}_{i, \cdot} \approx \boldsymbol{y}_{i, \cdot}\)&lt;/span&gt; in vector notation.
For each response &lt;span class="math"&gt;\(i\)&lt;/span&gt;, we solve the regression problem to obtain &lt;span class="math"&gt;\(\boldsymbol{h}_{i, \cdot}\)&lt;/span&gt;, the &lt;span class="math"&gt;\(i\)&lt;/span&gt;&lt;span class="quo"&gt;‘&lt;/span&gt;th row in &lt;span class="math"&gt;\(H\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This regression should be&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sparse, since then the posterior precision &lt;span class="math"&gt;\(\Lambda_x + H^T \tilde{\Lambda} H\)&lt;/span&gt; will retain most of the sparsity of the prior &lt;span class="math"&gt;\(\Lambda_x\)&lt;/span&gt;. Sparsity also means a more interpretable model.&lt;/li&gt;
&lt;li&gt;Fast, since both &lt;span class="math"&gt;\(m\)&lt;/span&gt; and &lt;span class="math"&gt;\(n\)&lt;/span&gt; can be huge.&lt;/li&gt;
&lt;li&gt;High dimensional, since &lt;span class="math"&gt;\(r\)&lt;/span&gt; is small compared to &lt;span class="math"&gt;\(m\)&lt;/span&gt; and &lt;span class="math"&gt;\(n\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some kind of Lasso or boosted linear regression is a good choice.
See Lunde (2025, Appendix E)&lt;sup id="fnref3:lunde2025"&gt;&lt;a class="footnote-ref" href="#fn:lunde2025"&gt;17&lt;/a&gt;&lt;/sup&gt; for more details.
The information criterion in Bühlmann (2006)&lt;sup id="fnref:buhlmann2006"&gt;&lt;a class="footnote-ref" href="#fn:buhlmann2006"&gt;23&lt;/a&gt;&lt;/sup&gt; could perhaps be used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Main computation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The algorithm inputs are &lt;span class="math"&gt;\(X_{\text{prior}}\)&lt;/span&gt;, an estimated &lt;span class="math"&gt;\(H\)&lt;/span&gt;, the observation noise covariance matrix &lt;span class="math"&gt;\(\Sigma_{\epsilon}\)&lt;/span&gt; and responses &lt;span class="math"&gt;\(Y := h(X_\text{prior})\)&lt;/span&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compute the residual matrix &lt;span class="math"&gt;\(R = Y - HX_{\text{prior}}\)&lt;/span&gt; and estimate &lt;span class="math"&gt;\(\Sigma_r\)&lt;/span&gt; from it. For instance by choosing &lt;span class="math"&gt;\(\Sigma_r\)&lt;/span&gt; to be diagonal, i.e., taking the variance over each row (response-residual) in &lt;span class="math"&gt;\(R\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Compute &lt;span class="math"&gt;\(\tilde{\Lambda} = \left( \Sigma_{\epsilon} + \Sigma_{r} \right)^{-1}\)&lt;/span&gt;. If either &lt;span class="math"&gt;\(\Sigma_{\epsilon}\)&lt;/span&gt; or &lt;span class="math"&gt;\(\Sigma_{r}\)&lt;/span&gt; is dense, then do not explicitly compute the inverse like this, but instead use e.g. Cholesky factorization to solve systems of equations. If they are both diagonal, then exploit that fact in computation instead of actually forming the diagonal matrix.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Solve&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{equation*}
( \Lambda_x + H^T \tilde{\Lambda} H ) Z = H^T \tilde{\Lambda}
(\boldsymbol{d} \boldsymbol{1}^T - (Y + E))
\end{equation*}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;or, equivalently, the column version for each column &lt;span class="math"&gt;\(j\)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;
&lt;div class="math"&gt;\begin{equation*}
( \Lambda_x + H^T \tilde{\Lambda} H ) \boldsymbol{z}_j = H^T \tilde{\Lambda}
(\boldsymbol{d} - (\boldsymbol{y}_j + \boldsymbol{\epsilon}_d))
\end{equation*}&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;If &lt;span class="math"&gt;\(\tilde{\Lambda}\)&lt;/span&gt; is diagonal, then exploit that fact. Since &lt;span class="math"&gt;\(( \Lambda_x + H^T \tilde{\Lambda} H )\)&lt;/span&gt; is sparse, large and symmetric positive definite, it can be solved with Cholesky or with Conjugate gradients.
4. Set &lt;span class="math"&gt;\(X_{\text{posterior}} = X_{\text{prior}} + Z\)&lt;/span&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="footnote"&gt;
&lt;hr/&gt;
&lt;ol&gt;
&lt;li id="fn:fn-innovation"&gt;
&lt;p&gt;If you want to jump ahead, the issue was that in equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt; the original formulation had &lt;span class="math"&gt;\(\boldsymbol{d} - \boldsymbol{y}\)&lt;/span&gt; in the innovation term instead of the correct &lt;span class="math"&gt;\(\boldsymbol{d} - (\boldsymbol{y} + \boldsymbol{\epsilon}_d)\)&lt;/span&gt;. &lt;a class="footnote-backref" href="#fnref:fn-innovation" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-logposterior"&gt;
&lt;p&gt;We can derive the posterior mean &lt;span class="math"&gt;\(\boldsymbol{\mu}_{x \mid d}\)&lt;/span&gt; by maximizing the log-posterior &lt;span class="math"&gt;\(\log ( p(\boldsymbol{x} \mid \boldsymbol{d}))\)&lt;/span&gt;, which is equal to &lt;span class="math"&gt;\(\log ( p(\boldsymbol{d} \mid \boldsymbol{x}) \, p(\boldsymbol{x}))\)&lt;/span&gt; (up to a constant) by Bayes’ theorem. Simply take the gradient of &lt;span class="math"&gt;\((\boldsymbol{d} - H \boldsymbol{x})^T \Sigma_\epsilon^{-1} (\boldsymbol{d} - H \boldsymbol{x}) + (\boldsymbol{x} - \boldsymbol{\mu}_x)^T \Sigma_x^{-1} (\boldsymbol{x} - \boldsymbol{\mu}_x)\)&lt;/span&gt; with respect to &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt;, equate it to &lt;span class="math"&gt;\(\boldsymbol{0}\)&lt;/span&gt; and solve. &lt;a class="footnote-backref" href="#fnref:fn-logposterior" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-pushthrough"&gt;
&lt;p&gt;A more direct, constructive way of showing this is to appeal to the &lt;em&gt;push-through identity&lt;/em&gt;, see for instance the Wikipedia page on the Woodbury matrix identity. &lt;a class="footnote-backref" href="#fnref:fn-pushthrough" title="Jump back to footnote 3 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-approx"&gt;
&lt;p&gt;This approximation comes without any guarantees, so engineering judgment is often required. We potentially perform an update so full of noise that keeping the prior would’ve been better. &lt;a class="footnote-backref" href="#fnref:fn-approx" title="Jump back to footnote 4 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-transpose"&gt;
&lt;p&gt;In statistics and machine learning, samples are typically associated with rows and features with columns. Here that convention is transposed; each parameter gets a row and each realization a column. &lt;a class="footnote-backref" href="#fnref:fn-transpose" title="Jump back to footnote 5 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-centering"&gt;
&lt;p&gt;Some authors write the centered matrix &lt;span class="math"&gt;\(\bar{X}\)&lt;/span&gt; as &lt;span class="math"&gt;\(X (I - \boldsymbol{1} \boldsymbol{1}^T / r)\)&lt;/span&gt;. While centering can be expressed as matrix multiplication, it is both hard to read and an unwise implementation strategy. &lt;a class="footnote-backref" href="#fnref:fn-centering" title="Jump back to footnote 6 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-bias"&gt;
&lt;p&gt;You might be wondering why we do not include a bias term in the approximation. It’s not needed, since it will cancel in the innovation term in &lt;span class="caps"&gt;ES&lt;/span&gt;-style updates like equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt; anyway. &lt;a class="footnote-backref" href="#fnref:fn-bias" title="Jump back to footnote 7 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:fn-residual"&gt;
&lt;p&gt;The reason we did not include residuals &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt; in the &lt;span class="caps"&gt;ES&lt;/span&gt; derivation is that they become part of &lt;span class="math"&gt;\(\Sigma_{y}\)&lt;/span&gt; in equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt; anyway. Under the assumption &lt;span class="math"&gt;\(\boldsymbol{y} = H \boldsymbol{x}\)&lt;/span&gt; we have &lt;span class="math"&gt;\(\Sigma_{y} = \operatorname{Cov}\left[ \boldsymbol{y} \right] = \operatorname{Cov}\left[ H \boldsymbol{x} \right] = H \Sigma_{x} H^T\)&lt;/span&gt;. Had we instead assumed &lt;span class="math"&gt;\(\boldsymbol{y} = H \boldsymbol{x} + \boldsymbol{r}\)&lt;/span&gt;, then &lt;span class="math"&gt;\(\Sigma_{y} = \operatorname{Cov}\left[ \boldsymbol{y} \right] = \operatorname{Cov}\left[ H \boldsymbol{x} + \boldsymbol{r} \right] = H \Sigma_{x} H^T + \Sigma_{r}\)&lt;/span&gt;. Either way, in the &lt;span class="caps"&gt;ES&lt;/span&gt; update equation &lt;span class="math"&gt;\(\eqref{eqn:ensemble_smoother_update}\)&lt;/span&gt; we rely on an empirical &lt;span class="math"&gt;\(\Sigma_{y}\)&lt;/span&gt;, which captures the residuals. It is only now that we wish to rely on &lt;span class="math"&gt;\(H\)&lt;/span&gt; instead of &lt;span class="math"&gt;\(\Sigma_{y}\)&lt;/span&gt; that we have to decompose &lt;span class="math"&gt;\(\Sigma_{y}\)&lt;/span&gt; into variance due to &lt;span class="math"&gt;\(\boldsymbol{x}\)&lt;/span&gt; mapped through &lt;span class="math"&gt;\(H\)&lt;/span&gt; (the &lt;span class="math"&gt;\(H \Sigma_{x} H^T\)&lt;/span&gt; term) and variance due to residuals (the &lt;span class="math"&gt;\(\Sigma_{r}\)&lt;/span&gt; term). &lt;a class="footnote-backref" href="#fnref:fn-residual" title="Jump back to footnote 8 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:evensen1994"&gt;
&lt;p&gt;G. Evensen, “Sequential data assimilation with a nonlinear quasi-geostrophic model using Monte Carlo methods to forecast error statistics,” &lt;em&gt;Journal of Geophysical Research&lt;/em&gt;, vol. 99, no. C5, pp. 10143–10162, 1994. &lt;a class="footnote-backref" href="#fnref:evensen1994" title="Jump back to footnote 9 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:vanleeuwen1996"&gt;
&lt;p&gt;&lt;span class="caps"&gt;P. J.&lt;/span&gt; Van Leeuwen and G. Evensen, “Data assimilation and inverse methods in terms of a probabilistic formulation,” &lt;em&gt;Monthly Weather Review&lt;/em&gt;, vol. 124, no. 12, pp. 2898–2913, 1996. &lt;a class="footnote-backref" href="#fnref:vanleeuwen1996" title="Jump back to footnote 10 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:burgers1998"&gt;
&lt;p&gt;G. Burgers, &lt;span class="caps"&gt;P. J.&lt;/span&gt; Van Leeuwen, and G. Evensen, “Analysis scheme in the ensemble Kalman filter,” &lt;em&gt;Monthly Weather Review&lt;/em&gt;, vol. 126, no. 6, pp. 1719–1724, 1998. &lt;a class="footnote-backref" href="#fnref:burgers1998" title="Jump back to footnote 11 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:chen2012"&gt;
&lt;p&gt;Y. Chen and &lt;span class="caps"&gt;D. S.&lt;/span&gt; Oliver, “Ensemble Randomized Maximum Likelihood Method as an Iterative Ensemble Smoother,” &lt;em&gt;Mathematical Geosciences&lt;/em&gt;, vol. 44, pp. 1–26, 2012. &lt;a class="footnote-backref" href="#fnref:chen2012" title="Jump back to footnote 12 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:chen2013"&gt;
&lt;p&gt;Y. Chen and &lt;span class="caps"&gt;D. S.&lt;/span&gt; Oliver, “Levenberg–Marquardt Forms of the Iterative Ensemble Smoother for Efficient History Matching and Uncertainty Quantification,” &lt;em&gt;Computational Geosciences&lt;/em&gt;, vol. 17, pp. 689–703, 2013. &lt;a class="footnote-backref" href="#fnref:chen2013" title="Jump back to footnote 13 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:emerick2013"&gt;
&lt;p&gt;&lt;span class="caps"&gt;A. A.&lt;/span&gt; Emerick and &lt;span class="caps"&gt;A. C.&lt;/span&gt; Reynolds, “Ensemble Smoother with Multiple Data Assimilation,” &lt;em&gt;Computers &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Geosciences&lt;/em&gt;, vol. 55, pp. 3–15, 2013. &lt;a class="footnote-backref" href="#fnref:emerick2013" title="Jump back to footnote 14 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:vossepoel2025"&gt;
&lt;p&gt;&lt;span class="caps"&gt;F. C.&lt;/span&gt; Vossepoel, G. Evensen, and &lt;span class="caps"&gt;P. J.&lt;/span&gt; van Leeuwen, “Adaptive Correlation- and Distance-Based Localization for Iterative Ensemble Smoothers in a Coupled Nonlinear Multiscale Model,” &lt;em&gt;Monthly Weather Review&lt;/em&gt;, vol. 153, no. 11, pp. 2593–2609, 2025. &lt;a class="footnote-backref" href="#fnref:vossepoel2025" title="Jump back to footnote 15 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:hastie2009"&gt;
&lt;p&gt;T. Hastie, R. Tibshirani, and J. Friedman, &lt;em&gt;The Elements of Statistical Learning: Data Mining, Inference, and Prediction&lt;/em&gt;, 2nd ed. New York: Springer, 2009. &lt;a class="footnote-backref" href="#fnref:hastie2009" title="Jump back to footnote 16 in the text"&gt;↩&lt;/a&gt;&lt;a class="footnote-backref" href="#fnref2:hastie2009" title="Jump back to footnote 16 in the text"&gt;↩&lt;/a&gt;&lt;a class="footnote-backref" href="#fnref3:hastie2009" title="Jump back to footnote 16 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:lunde2025"&gt;
&lt;p&gt;B. Å. S. Lunde, “An Ensemble Information Filter: Retrieving Markov-information from the &lt;span class="caps"&gt;SPDE&lt;/span&gt; Discretisation,” arXiv:2501.09016, 2025. &lt;a class="footnote-backref" href="#fnref:lunde2025" title="Jump back to footnote 17 in the text"&gt;↩&lt;/a&gt;&lt;a class="footnote-backref" href="#fnref2:lunde2025" title="Jump back to footnote 17 in the text"&gt;↩&lt;/a&gt;&lt;a class="footnote-backref" href="#fnref3:lunde2025" title="Jump back to footnote 17 in the text"&gt;↩&lt;/a&gt;&lt;a class="footnote-backref" href="#fnref4:lunde2025" title="Jump back to footnote 17 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:petersen2012"&gt;
&lt;p&gt;&lt;span class="caps"&gt;K. B.&lt;/span&gt; Petersen and &lt;span class="caps"&gt;M. S.&lt;/span&gt; Pedersen, &lt;em&gt;The Matrix Cookbook&lt;/em&gt;, Technical University of Denmark, version 20121115, 2012. &lt;a class="footnote-backref" href="#fnref:petersen2012" title="Jump back to footnote 18 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:bishop2006"&gt;
&lt;p&gt;&lt;span class="caps"&gt;C. M.&lt;/span&gt; Bishop, &lt;em&gt;Pattern Recognition and Machine Learning&lt;/em&gt;. New York: Springer, 2006. &lt;a class="footnote-backref" href="#fnref:bishop2006" title="Jump back to footnote 19 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:emerick2012"&gt;
&lt;p&gt;&lt;span class="caps"&gt;A. A.&lt;/span&gt; Emerick and &lt;span class="caps"&gt;A. C.&lt;/span&gt; Reynolds, “History matching time-lapse seismic data using the ensemble Kalman filter with multiple data assimilations,” &lt;em&gt;Computational Geosciences&lt;/em&gt;, vol. 16, pp. 639–659, 2012. &lt;a class="footnote-backref" href="#fnref:emerick2012" title="Jump back to footnote 20 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:emerick2016"&gt;
&lt;p&gt;&lt;span class="caps"&gt;A. A.&lt;/span&gt; Emerick, “Analysis of the performance of ensemble-based assimilation of production and seismic data,” &lt;em&gt;Journal of Petroleum Science and Engineering&lt;/em&gt;, vol. 139, pp. 219–239, 2016. &lt;a class="footnote-backref" href="#fnref:emerick2016" title="Jump back to footnote 21 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:rue2005"&gt;
&lt;p&gt;H. Rue and L. Held, &lt;em&gt;Gaussian Markov Random Fields: Theory and Applications&lt;/em&gt;. New York: Chapman and Hall/&lt;span class="caps"&gt;CRC&lt;/span&gt;, 2005. &lt;a class="footnote-backref" href="#fnref:rue2005" title="Jump back to footnote 22 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:buhlmann2006"&gt;
&lt;p&gt;P. Bühlmann, “Boosting for High-Dimensional Linear Models,” &lt;em&gt;Annals of Statistics&lt;/em&gt;, vol. 34, 2006. &lt;a class="footnote-backref" href="#fnref:buhlmann2006" title="Jump back to footnote 23 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sat, 01 Aug 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-08-01:/articles/2026/notes-on-ensemble-smoothers</guid><category>articles</category><category>mathematics</category><category>statistics</category></item><item><title>Hvordan fungerer opptak til høyere utdanning?</title><link>https://tommyodland.com/articles/2026/hvordan-fungerer-opptak-til-hoyere-utdanning</link><description>&lt;p&gt;I Norge har vi en svært god prosess for opptak til høyere utdanning.
Ved å ta i bruk en sentralisert mekanisme har vi laget et system som er raskt, objektivt og korrupsjonsfritt.
Dette systemet, kalt &lt;a href="https://www.samordnaopptak.no/"&gt;Samordna Opptak&lt;/a&gt;, fordeler hvert år omtrent 150 000 søkere til 1 400 studier.
Hver søker sender simpelthen inn en rangert liste over sine ønsker, og en algoritme fordeler deretter studieplassene.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/opptak_universitet/opptak_universitet_sentral_mekanisme.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;I denne artikkelen skal vi se nærmere på følgende spørsmål:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hva er fordelene med et &lt;em&gt;sentralisert opptak&lt;/em&gt;? Hvorfor gjør vi ikke som &lt;span class="caps"&gt;USA&lt;/span&gt; og desentraliserer opptaket?&lt;/li&gt;
&lt;li&gt;Hvordan fungerer egentlig algoritmen? Den er tross alt med på å bestemme fremtiden til over hundre tusen mennesker hvert eneste år.&lt;/li&gt;
&lt;li&gt;Hvilke &lt;em&gt;effektivitetsgevinster&lt;/em&gt; gir man fra seg i algoritmevalget? Og på hvilken måte mister vi effektivitet ved å bruke kjønnspoeng og kvoter?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#sentralisert-vs-desentralisert-opptak"&gt;Sentralisert vs. desentralisert opptak&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#desentralisert-opptak-i-usa"&gt;Desentralisert opptak i &lt;span class="caps"&gt;USA&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#forutsetninger-for-bruk-av-algoritmer"&gt;Forutsetninger for bruk av algoritmer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#algoritmen-utsatt-aksept"&gt;Algoritmen: Utsatt Aksept&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#eksempel-utsatt-aksept"&gt;Eksempel: Utsatt Aksept&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#pareto-effektivitet"&gt;Pareto-effektivitet&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#ineffektivitet-grunnet-stabilitetskrav"&gt;Ineffektivitet grunnet stabilitetskrav&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ineffektivitet-grunnet-kjnnspoeng"&gt;Ineffektivitet grunnet kjønnspoeng&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#oppsummering-og-referanser"&gt;Oppsummering og referanser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#appendiks-a-digitalisering-algoritmer-og-offentlig-sektor"&gt;Appendiks A: Digitalisering, algoritmer og offentlig sektor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#appendiks-b-boston-mekanismen"&gt;Appendiks B: Boston-mekanismen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="sentralisert-vs-desentralisert-opptak"&gt;Sentralisert vs. desentralisert opptak&lt;/h2&gt;
&lt;p&gt;At opptaket er &lt;em&gt;sentralisert&lt;/em&gt; betyr at søkere forholder seg til en sentral autoritet, heller enn å individuelt søke til hvert studie.
Ifølge en &lt;a href="https://thedocs.worldbank.org/en/doc/fa6ab76559c14f38def82e2cedf3b446-0050022024/original/Education-Markets-Around-the-World-Chris-Neilson.pdf"&gt;Verdensbank-rapport&lt;/a&gt; (2024) bruker omtrent 90 land sentralisert opptak til høyere utdanning.
De fleste av disse er rike, industrialiserte land.&lt;/p&gt;
&lt;p&gt;I Norge har vi hatt sentralisert opptak siden midten av 90-tallet.
Det er lett å ta gevinstene for gitt, men i enkelte andre land er opptaksprosessen fremdeles manuell, og derfor langt mer krevende å navigere, samt mindre transparent.&lt;/p&gt;
&lt;h3 id="desentralisert-opptak-i-usa"&gt;Desentralisert opptak i &lt;span class="caps"&gt;USA&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;I &lt;span class="caps"&gt;USA&lt;/span&gt; søker man individuelt til hvert studie eller universitet.
Hvert studie rangerer søkere etter til dels subjektive kriterier, som ofte heller ikke er allmennkjente (essay, fritidsaktiviteter, diverse kvoter, osv).
Det er flere runder, f.eks. er det ofte en “early admissions” runde først.
Dette er et uoversiktlig og komplekst system for alle parter, og en kommersiell industri har oppstått som direkte resultat av dette.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/opptak_universitet/opptak_universitet_desentralisert.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;I &lt;span class="caps"&gt;USA&lt;/span&gt; er studiestedene aktive strategiske aktører som tilbyr plasser i håp om å kapre de beste studentene.
Studenter må også tenke strategisk: “bør jeg akseptere et tilbud nå, i tilfelle jeg ikke får et bedre tilbud senere?”
I Norge trenger ikke studenter å tenke strategisk i det hele tatt—de oppgir bare en rangert liste og algoritmen ordner resten.
Dette er fordi algoritmen som brukes i Norge er &lt;a href="https://en.wikipedia.org/wiki/Strategyproofness"&gt;strategisikker&lt;/a&gt; (man kan ikke oppnå et bedre resultat ved å oppgi en uærlig rangering), og fordi vi har felles tidsfrister som alle studiesteder må forholde seg til.&lt;/p&gt;
&lt;h3 id="forutsetninger-for-bruk-av-algoritmer"&gt;Forutsetninger for bruk av algoritmer&lt;/h3&gt;
&lt;p&gt;Det sitter selvsagt ikke ansatte på et kontor og manuelt fordeler hundre tusen studieplasser.
Jobben gjøres av en &lt;em&gt;algoritme&lt;/em&gt;: en veldefinert sekvens av konkrete steg.
I Norge har vi vært kloke nok til å koke opptaksprosessen ned til noen få objektive kriterier, f.eks. karaktersnitt og alderspoeng, som en algoritme kan prosessere.&lt;/p&gt;
&lt;p&gt;Forutsetningene for å kunne bruke en algoritme godt i slike settinger er:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alle studiesteder må være med. Vi bør ikke ha parallelle opptaksprosesser på siden.&lt;/li&gt;
&lt;li&gt;Rangeringskriteriene må være transparente, objektive og ikke for kompliserte.&lt;/li&gt;
&lt;li&gt;Selve algoritmen må være god.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Påstander som “ikke for kompliserte” og “algoritmen må være god” er vage.
I resten av artikkelen skal vi konkretisere disse utsagnene og se på et enkelt eksempel.&lt;/p&gt;
&lt;h2 id="algoritmen-utsatt-aksept"&gt;Algoritmen: Utsatt Aksept&lt;/h2&gt;
&lt;p&gt;Algoritmen som brukes i Samordna Opptak er med høy sannsynlighet en variant av &lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Gale%E2%80%93Shapley_algorithm"&gt;Deferred Acceptance&lt;/a&gt;&lt;/em&gt;, eller &lt;em&gt;Utsatt Aksept&lt;/em&gt; på norsk.
Jeg fant dessverre ingen offisiell kilde som bekrefter dette, så det er kvalifisert gjetning.
Likevel er jeg rimelig sikker, fordi algoritmen brukes av mange andre land, og &lt;a href="https://no.wikipedia.org/wiki/Samordna_opptak"&gt;Wikipedia&lt;/a&gt; hinter til Utsatt Aksept.
Dette er en utmerket algoritme med gode egenskaper (&lt;em&gt;strategisikkerhet&lt;/em&gt; og &lt;em&gt;fri for misunnelse&lt;/em&gt;), som brukes verden over til lignende opptaksprosesser.&lt;/p&gt;
&lt;div class="algorithm" id="algorithm_utsatt_aksept" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Algoritme (Utsatt Aksept).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Input til algoritmen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;for hver student, en rangert liste over studier&lt;/li&gt;
&lt;li&gt;for hvert studie, en rangert liste over studenter&lt;/li&gt;
&lt;li&gt;for hvert studie, en kapasitet&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Studenter fordeles på studier i tre steg:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Hver student søker på sitt førsteønske, og hvert studie tar tentativt inn studentene som er høyest rangert, mens resten blir avvist.&lt;/li&gt;
&lt;li&gt;Hver avviste student søker på sitt neste ønske, og hvert studie ser på både de nye søkerne og de som allerede er tentativt akseptert.
   Hver studie tar inn studentene som er rangert høyest, mens resten blir avvist.&lt;/li&gt;
&lt;li&gt;Steg 2 gjentas til alle studenter enten er akseptert eller har blitt avvist fra alle studier.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;h3 id="eksempel-utsatt-aksept"&gt;Eksempel: Utsatt Aksept&lt;/h3&gt;
&lt;p&gt;Her er et eksempel med 4 studenter og 3 studier som viser hvordan Utsatt Aksept fungerer.
Vi ser bort fra kvotene, og tar kun med karakterpoeng og kjønnspoeng.
I virkeligheten har vi også språkpoeng, realfagspoeng, alderspoeng og tilleggspoeng, men dette endrer ikke hvordan algoritmen fungerer—det endrer kun studienes rangering av studentene.&lt;/p&gt;
&lt;p&gt;Her er et lite datasett med søkere og studieplasser:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;Søkere&lt;/span&gt;
&lt;span class="gu"&gt;------&lt;/span&gt;
Anna  (poeng: 42): ['Sykepleie', 'Lærer', 'Ingeniør']
Eva   (poeng: 42): ['Lærer', 'Sykepleie', 'Ingeniør']
Elias (poeng: 41): ['Lærer', 'Ingeniør', 'Sykepleie']
Emma  (poeng: 40): ['Lærer', 'Ingeniør', 'Sykepleie']

&lt;span class="gu"&gt;Studieplasser&lt;/span&gt;
&lt;span class="gu"&gt;-------------&lt;/span&gt;
Sykepleie: 1 plass
Ingeniør:  1 plass
Lærer:     1 plass
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ettersom vi inkluderer kjønnspoeng, antar vi at følgende tilleggspoeng:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kvinner får 2 kjønnspoeng når de søker ingeniør.&lt;/li&gt;
&lt;li&gt;Menn får 2 kjønnspoeng når de søker sykepleie.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Uten kjønnspoeng ville studienes rangering vært karakterbasert og helt identisk, altså &lt;code&gt;['Anna', 'Eva', 'Elias', 'Emma']&lt;/code&gt; for alle studiene.
Dersom flere studenter har likt antall poeng, avgjør vi rekkefølgen alfabetisk.
I virkeligheten er det alder som brukes som &lt;em&gt;tie-breaker&lt;/em&gt;, se &lt;a href="https://lovdata.no/forskrift/2017-01-06-13/§7-1"&gt;$ 7-1 (4) i forskrift om opptak til høgare utdanning&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Med kjønnspoeng tatt i betraktning rangerer hvert studie søkerne slik:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;Studieplassenes rangering&lt;/span&gt;
&lt;span class="gu"&gt;-------------------------&lt;/span&gt;
Sykepleie: 'Elias', 'Anna', 'Eva', 'Emma'
Ingeniør:  'Anna', 'Eva', 'Emma', 'Elias'
Lærer:     'Anna', 'Eva', 'Elias', 'Emma'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nå har både studentene og studiene en rangering av hverandre, og vi kan kjøre Utsatt Aksept.
Her er stegene som algoritmen utfører, forklart i detalj:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Anna søker sykepleie og blir akseptert. Eva, Elias og Emma søker lærer. Lærerstudiet rangerer Eva høyest av disse tre og aksepterer henne. Elias og Emma blir avvist fra lærerstudiet.&lt;/li&gt;
&lt;li&gt;Elias og Emma søker begge ingeniør. Ingeniørstudiet rangerer Emma høyest av disse to og aksepterer henne. Elias blir avvist.&lt;/li&gt;
&lt;li&gt;Elias søker sykepleie. Sykepleie rangerer Elias foran Anna (som har plassen tentativt), så Elias blir akseptert og Anna blir avvist.&lt;/li&gt;
&lt;li&gt;Anna søker lærer. Lærerstudiet rangerer Anna foran Eva, så Anna blir akseptert og Eva blir avvist.&lt;/li&gt;
&lt;li&gt;Eva søker sykepleie. Sykepleie rangerer Elias foran Eva, så Eva blir avvist.&lt;/li&gt;
&lt;li&gt;Eva søker ingeniør. Ingeniør rangerer Eva foran Emma, så Emma blir avvist.&lt;/li&gt;
&lt;li&gt;Emma søker sykepleie. Sykepleie rangerer Elias foran Emma, så Emma blir avvist. Emma har nå blitt avvist fra alle studiene, og algoritmen er ferdig.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Resultatet blir følgende fordeling:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;Studieplassenes opptak&lt;/span&gt;
&lt;span class="gu"&gt;----------------------&lt;/span&gt;
Sykepleie: 'Elias'  (3. ønske)
Ingeniør:  'Eva'    (3. ønske)
Lærer:     'Anna'   (2. ønske)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ingen fikk sitt førsteønske, og bare Anna fikk sitt andreønske.
Emma fikk ikke studieplass i det hele tatt.&lt;/p&gt;
&lt;h2 id="pareto-effektivitet"&gt;Pareto-effektivitet&lt;/h2&gt;
&lt;p&gt;Du synes kanskje ikke at løsningen ovenfor var særlig god?
Tross alt fikk ingen sitt førsteønske, og bare én person fikk sitt andreønske.
En måte å kvantifisere om en løsning er god på, er så sjekke om den er &lt;em&gt;Pareto-effektiv&lt;/em&gt; (eller bare &lt;em&gt;effektiv&lt;/em&gt;).&lt;/p&gt;
&lt;div class="definition" id="definition_pareto_efficient" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Definisjon (Pareto-effektivitet).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En fordeling er Pareto-effektiv dersom ingen kan bytte plass og bli mer fornøyde, uten at det går ut over noen andre.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Fordelingen ovenfor er ikke Pareto-effektiv, fordi både Anna og Elias ville blitt mer fornøyde om de byttet plass.&lt;/p&gt;
&lt;h3 id="ineffektivitet-grunnet-stabilitetskrav"&gt;Ineffektivitet grunnet stabilitetskrav&lt;/h3&gt;
&lt;p&gt;Elias og Anna rangerte studiene slik:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;Søkere&lt;/span&gt;
&lt;span class="gu"&gt;------&lt;/span&gt;
Anna  (poeng: 42): ['Sykepleie', 'Lærer', 'Ingeniør']
Elias (poeng: 41): ['Lærer', 'Ingeniør', 'Sykepleie']
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;På tross av at Anna ønsker å bli sykepleier og Elias ønsker å bli lærer, ender Anna opp på lærerstudiet og Elias opp på sykepleierstudiet.
&lt;strong&gt;Dersom Anna og Elias bytter plass, får begge to sitt førsteønske!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hvorfor kan vi ikke bare la de bytte plass?
Det finnes en algoritme som baserer seg på slike bytter, den kalles &lt;em&gt;Bytteringer&lt;/em&gt; (&lt;em&gt;Top Trading Cycles&lt;/em&gt; på Engelsk).
Om vi bruker Bytteringer ville vi fått løsningen:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;Studieplassenes opptak&lt;/span&gt;
&lt;span class="gu"&gt;----------------------&lt;/span&gt;
Sykepleie: 'Anna'  (1. ønske)
Ingeniør:  'Eva'   (3. ønske)
Lærer:     'Elias' (1. ønske)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Problemet med å innfri byttet er at da vil Eva bli misunnelig på Elias.
Når &lt;em&gt;misunnelse&lt;/em&gt; (eller &lt;em&gt;justified envy&lt;/em&gt; på Engelsk) oppstår, sier vi at løsningen ikke er &lt;em&gt;stabil&lt;/em&gt;, fordi en student og ett studie foretrekker begge hverandre fremfor hva de ble tildelt.&lt;/p&gt;
&lt;p&gt;Her er problemet med fordelingen vi får dersom vi innfrir byttet:
Eva rangerte lærerstudiet foran ingeniørstudiet.
Lærerstudiet rangerte Eva foran Elias.
Elias har “tatt” plassen som Eva hadde prioritet på hos lærerstudiet!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dette illustrerer den uunngåelige avveiningen mellom å prioritere enkeltindividers rettigheter til å ikke bli forbigått i køen (når rangeringen til studieplassene tolkes som en kø) og å innfri Pareto-effektivitet.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Algoritmen &lt;em&gt;Utsatt Aksept&lt;/em&gt; respekterer enkeltindividers rettigheter i prioriteringslisten på bekostning av Pareto-effektivitet.&lt;/li&gt;
&lt;li&gt;Algoritmen &lt;em&gt;Bytteringer&lt;/em&gt; garanterer Pareto-effektivitet, men respekterer ikke enkeltindividers rettigheter (den kan skape misunnelse).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Det finnes ingen løsning på dette problemet som både (1) unngår at studenter blir misunnelige og (2) er Pareto-effektive.
Det finnes derfor heller ingen algoritme som kan innfri begge disse egenskapene samtidig for et vilkårlig problem.
I de aller fleste land, inkludert i Norge, bruker man Utsatt Aksept fordi man anser (1) som viktigere enn (2).&lt;/p&gt;
&lt;h3 id="ineffektivitet-grunnet-kjnnspoeng"&gt;Ineffektivitet grunnet kjønnspoeng&lt;/h3&gt;
&lt;p&gt;Det er en annen kilde til ineffektivitet: kjønnspoeng.&lt;/p&gt;
&lt;p&gt;Enten man er for eller i mot kjønnspoeng politisk, så er det greit å være klar over at kjønnspoeng ofte overstyrer individers egne ønsker.
Ved å bruke kjønnspoeng ignorerer staten til en viss grad individers ønsker og ofrer effektivitet for å innfri politiske mål rundt kjønnsbalanse.&lt;/p&gt;
&lt;p&gt;Uten kjønnspoeng ville studiene rangert studentene identisk etter kun karaktersnitt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;Studieplassenes rangering&lt;/span&gt;
&lt;span class="gu"&gt;-------------------------&lt;/span&gt;
Sykepleie: 'Anna', 'Eva', 'Elias', 'Emma'
Ingeniør:  'Anna', 'Eva', 'Elias', 'Emma'
Lærer:     'Anna', 'Eva', 'Elias', 'Emma'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Løsningen ville blitt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;Studieplassenes opptak (uten kjønnspoeng)&lt;/span&gt;
&lt;span class="gu"&gt;-----------------------------------------&lt;/span&gt;
Sykepleie: 'Anna'  (1. ønske)
Ingeniør:  'Elias' (2. ønske)
Lærer:     'Eva'   (1. ønske)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Dette er en bedre løsning enn den vi fikk med kjønnspoeng; faktisk blir alle tre studentene mer fornøyde.&lt;/p&gt;
&lt;p&gt;Blir samfunnet bedre av å gi Eva hennes førsteønske (lærer), eller av å sende henne til tredjeønsket (ingeniør) fordi hun er kvinne og vil bidra til kjønnsbalanse blant ingeniører?
Det er et politisk spørsmål, og din rettferdighetssans er med på å bestemme svaret.&lt;/p&gt;
&lt;h2 id="oppsummering-og-referanser"&gt;Oppsummering og referanser&lt;/h2&gt;
&lt;p&gt;De fleste land bruker i dag sentraliserte opptak til høyere utdanning, og Norge er ett av dem.
Det store unntaket er &lt;span class="caps"&gt;USA&lt;/span&gt;, der privat rådgivning i opptaksprosessen er en egen bransje som håver inn omtrent &lt;a href="https://www.thecrimson.com/article/2025/11/27/college-consultants-admissions-scrutiny/"&gt;30 milliarder &lt;span class="caps"&gt;NOK&lt;/span&gt; per år&lt;/a&gt;.
Algoritmen som sannsynligvis brukes i Norge er Utsatt Aksept, som er en variant av &lt;a href="https://en.wikipedia.org/wiki/Gale%E2%80%93Shapley_algorithm"&gt;Gale-Shapley algoritmen&lt;/a&gt;.
Den er strategisikker og stabil, men ikke Pareto-effektiv.&lt;/p&gt;
&lt;p&gt;Når alle studiene rangerer søkerne identisk—som er tilfellet om man kun baserer seg på karakterpoeng—så har vi Pareto-effektivitet: ingen grupper kan innbyrdes bytte plass og bli mer fornøyde.
Dersom studiene derimot har forskjellige rangeringer av søkerne, f.eks. på grunn av kjønnspoeng, vil det oppstå et effektivitetstap.
Noen søkere vil ønske å innbyrdes bytte plass, men staten sier implisitt at deres ønsker må vike—selv når et bytte ville vært win-win for de involverte og ikke gått ut over noen andre.&lt;/p&gt;
&lt;!-- (En teknisk kommentar: vilkårlig tie-breaking av studenter med samme karaktersnitt i rekkefølgen er også en kilde til ineffektivitet, men det finnes ingen algoritme som både fjerner denne ineffektiviteten og samtidig bevarer strategisikkerhet. Algoritmen som fjerner denne ineffekiviteten er derimot ikke _åpenbart manipulerbar_.) --&gt;
&lt;p&gt;To gode referanser, skrevet av de samme forskerne, er:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://people.duke.edu/~aa88/articles/WorldCongressSurvey.pdf"&gt;Matching Markets: Theory and Practice&lt;/a&gt;. Abdulkadiroğlu, Atila, and Tayfun Sönmez. 2013. Se spesielt seksjon 4.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aeaweb.org/articles?id=10.1257%2F000282803322157061"&gt;School Choice: A Mechanism Design Approach&lt;/a&gt;. Abdulkadiroğlu, Atila, and Tayfun Sönmez. 2003.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Min egen artikkel &lt;a href="https://tommyodland.com/files/storage/bedre_skole_nr3_2022_odland_murray.pdf"&gt;Mangelfull evaluering av inntaksmodeller&lt;/a&gt; kan også være interessant.&lt;/p&gt;
&lt;h2 id="appendiks-a-digitalisering-algoritmer-og-offentlig-sektor"&gt;Appendiks A: Digitalisering, algoritmer og offentlig sektor&lt;/h2&gt;
&lt;p&gt;Et hjertesukk: på tross av mye prat om digitalisering og effektivisering i offentlig sektor, er det for lite fokus på enkle tiltak med høy nytteverdi.&lt;/p&gt;
&lt;p&gt;Anta at Samordna Opptak sparer hver søker for om lag 10 timers arbeid, sammenlignet med individuelle søknader til hvert studie.
Det tilsvarer omtrent 500 &lt;span class="caps"&gt;MNOK&lt;/span&gt; hvert eneste år.
Dette er lite sammenlignet med den samfunnsøkonomiske genvisten av å ha en transparent, ikke-strategisk prosess som allokerer talent til riktig sted.
(Om karakterer er ensbetydende med talent kan diskuteres, men prosessen er i hvert fall fri for korrupsjon, vennetjenester og pengemakt.)
Samordna Opptak er fantastisk god digitalisering og algoritmebruk.&lt;/p&gt;
&lt;p&gt;Samtidig står det i en &lt;a href="https://cms.sikt.no/sites/default/files/2022-12/Satsingsforslag%20Fremtidens%20opptak.pdf"&gt;rapport&lt;/a&gt; fra 2022 at datasystemene til Samordna Opptak ikke har “&lt;em&gt;fulgt den teknologiske utviklingen&lt;/em&gt;”.
Deler av systemet er utviklet og vedlikeholdt av én og samme person.
Rapporten sier at “&lt;em&gt;faren for total kollaps blir større for hver dag som går&lt;/em&gt;”.&lt;/p&gt;
&lt;p&gt;Samfunnet kunne tjent mye på å bruke algoritmer for sentraliserte opptak i andre situasjoner.
Noen åpenbare eksempler er:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;barnehageopptak&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2025/fastlegebytter"&gt;fastlegebytter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;opptak til militæret&lt;/li&gt;
&lt;li&gt;fordeling av studentboliger&lt;/li&gt;
&lt;li&gt;interne jobbmarkeder&lt;/li&gt;
&lt;li&gt;tildeling av praksisplasser til studenter&lt;/li&gt;
&lt;li&gt;og mye mer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Du kan lese mer om dette på &lt;strong&gt;&lt;a href="https://alloker.no/"&gt;Alloker.no&lt;/a&gt;&lt;/strong&gt;, som er et prosjekt jeg har jobbet med som implementerer algoritmer som Utsatt Aksept bak et &lt;span class="caps"&gt;API&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="appendiks-b-boston-mekanismen"&gt;Appendiks B: Boston-mekanismen&lt;/h2&gt;
&lt;p&gt;Boston-mekanismen er en algoritme som &lt;a href="https://alloker.no/page/boston-mechanism/"&gt;ikke bør brukes&lt;/a&gt;.
Den fungerer slik:&lt;/p&gt;
&lt;div class="algorithm" id="algorithm_utsatt_aksept" style="border:1px solid #000;border-radius:4px;padding:0.6em 1em;margin:1.2em 0;"&gt;
&lt;p&gt;&lt;strong&gt;Algoritme (Boston-mekanismen).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Studentene sender inn en rangert liste over studiene de ønsker. 
Algoritmen behandler så personene i iterasjoner:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Alle studentenes førsteønsker vurderes. Hvert studie fyller plassene sine med de høyest prioriterte studentene blant dem som har studiet som førsteønske. Studenter som ikke får plass, avvises.&lt;/li&gt;
&lt;li&gt;Avviste studenter vurderes etter sitt andreønske, men kun for gjenværende plasser på studiene etter forrige iterasjon.&lt;/li&gt;
&lt;li&gt;Slik fortsetter prosessen til studieplassene er fylt opp.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;Dersom vi kjører algoritmen på vårt eksempel:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sykepleierstudiet velger Anna. Lærerstudiet velger Eva. Elias og Emma avvises.&lt;/li&gt;
&lt;li&gt;Ingeniørstudiet velger Emma. Elias avvises. Alle studieplassene er nå fylt opp.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Løsningen blir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gu"&gt;Studieplassenes opptak (Boston-mekanismen)&lt;/span&gt;
&lt;span class="gu"&gt;------------------------------------------&lt;/span&gt;
Sykepleie: 'Anna'  (1. ønske)
Ingeniør:  'Emma'  (2. ønske)
Lærer:     'Eva'   (1. ønske)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Dette er en god løsning i den forstand at mange får sine ønsker oppfylt.
Dessverre er mekanismen ikke strategisikker, så vi kan ikke anta at de oppgitte ønskene er de reelle ønskene når en slik algoritme blir brukt i virkeligheten.
Å bruke Boston-mekanismen, eller å prøve å finne opp sin egen algoritme, er en dårlig idé.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 20 Jul 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-07-20:/articles/2026/hvordan-fungerer-opptak-til-hoyere-utdanning</guid><category>articles</category><category>algorithms</category></item><item><title>KI-billettkontroll</title><link>https://tommyodland.com/articles/2026/ki-billettkontroll</link><description>&lt;p&gt;Dersom 5% sniker på bussen i område A, og 2% sniker i område B - hvordan bør Ruter fordele billettkontrollørene sine?&lt;/p&gt;
&lt;p&gt;De kan f.eks.: 
(a) sende alle til område A for å ta flest mulige snikere, 
(b) fordele kontrollørene proporsjonalt eller 
(c) se bort fra historiske data og sende halvparten til hvert område.
Ulempen med (a) er at publikum etter hvert vil skjønne at ingen lenger kontrollerer område B.
Ulempen med (c) er at man tar færre snikere enn hva man kunne gjort.
En mellomting, noe sånn som (b), er nok mest fornuftig.
(Alle tre løsningene kan parametriseres.)&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/ki_billettkontroll/Jernbanetorget_Oslo_bus_tower_Norway_2016-09-17.jpg" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 380px;"/&gt;&lt;/p&gt;
&lt;p&gt;Denne problemstillingen skapte debatt da Ruter-sjefen &lt;a href="https://www.aftenposten.no/oslo/i/M7L6RM/ruter-sjefen-spaar-fremtiden-har-tro-paa-at-tre-ting-vil-slaa-til"&gt;sa til Aftenposten&lt;/a&gt; at deres &lt;span class="caps"&gt;KI&lt;/span&gt;-modell “forurenses med tilfeldige data for å gjøre billettkontrollene sosialt akseptable.”
Det er uklart hva “sosialt akseptable” betyr, men noen politikere reagerte, og det kom et &lt;a href="https://www.minerva.no/kollektivtransport-kunstig-intelligens-oslo/planlegging-av-billettkontroll-besluttes-av-mennesker-og-stottes-av-ki/509141"&gt;tilsvar fra Ruter&lt;/a&gt;.
Der sier de, med ganske mange ord, at de gjør noe som ligner på (b):
de gjennomfører kontroller bredt, samtidig som de prioriterer flere kontroller i områder med høyere snikandel.&lt;/p&gt;
&lt;p&gt;Jeg &lt;a href="https://tommyodland.com/articles/2023/fair-ticket-controls"&gt;skrev om Ruters &lt;span class="caps"&gt;KI&lt;/span&gt;-billettkontroll&lt;/a&gt; for tre år siden, i 2023, og la frem 8 ulike løsninger på problemstillingen som alle er i konflikt med hverandre—det finnes ikke én rettferdig løsning.&lt;/p&gt;
&lt;p&gt;Ruter burde utdypet vage begreper som “&lt;span class="caps"&gt;KI&lt;/span&gt;-modell” og “sosialt akseptabelt”, og vært mer konkrete om algoritmene de bruker og målene de ønsker å oppnå.
Det kunne ha startet en interessant diskusjon om algoritmer og kontroll.
Når Skatteetaten, Mattilsynet eller Lånekassen utfører kontroll, bør de velge (a), (b) eller (c)?
Er tilfeldig kontroll det eneste rettferdige, fordi alle individer skal ha like lite mistanke rettet mot seg a priori?
Eller kan erfaringsdata brukes til å effektivisere kontroller?
Hvis ja, i hvor stor grad? Kan alle typer data brukes?
Hva skal epsilon være i en epsilon-grådig strategi?&lt;/p&gt;
&lt;p&gt;Dette er god bruk av grunnleggende statisti… ehm… &lt;span class="caps"&gt;KI&lt;/span&gt;-modeller, og kunne nok ha effektivisert en del kontrollprosesser—men da må man først bli enige om hva man vil oppnå.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sun, 19 Jul 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-07-19:/articles/2026/ki-billettkontroll</guid><category>articles</category><category>datascience</category></item><item><title>Ten lessons from twenty years of strength training</title><link>https://tommyodland.com/articles/2026/ten-lessons-from-twenty-years-of-strength-training</link><description>&lt;p&gt;I started strength training in 2006.
Over twenty years I’ve learned that the principles of strength are simple.
Almost everything you need to know can be summarized in two pages.
Following the ten lessons below will make you stronger than 90-99% of the population.
Here are my two pages:&lt;/p&gt;
&lt;hr/&gt;
&lt;h3 id="start-easy"&gt;Start easy&lt;/h3&gt;
&lt;p&gt;Start with light weights if you’re a beginner.
During the first months, establish a routine of going to the gym and learn to lift correctly.
Don’t rush into heavy weights—you’ll have plenty of time for that later.&lt;/p&gt;
&lt;p&gt;The same advice applies if you’re getting back to lifting after a break.
When gyms re-opened after &lt;span class="caps"&gt;COVID&lt;/span&gt;-19, I lifted the bar on my first workout.
Then I increased to 40 kg, then 60 kg, 70 kg, etc.
Start too heavy and you might get injured, or experience debilitating soreness.
Either one hinders the steady progress required to build strength.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/twenty_years_of_strength/2010_EM_squat.JPG" style="display: block; margin-left: auto; margin-right: auto; max-width: 400px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p style="text-align: center; font-style: italic;"&gt;Squatting 285 kg at 108 kg bodyweight in 2010.&lt;/p&gt;
&lt;h3 id="stay-consistent"&gt;Stay consistent&lt;/h3&gt;
&lt;p&gt;Priority number one is to keep training.
Half the game is showing up 2-4 times a week.
If you get injured, come up with a plan to train around it.
Set your ego aside.
Don’t risk training through pain.&lt;/p&gt;
&lt;p&gt;You must also avoid burnout.
Hitting the gym six days a week and eating like a bodybuilder can be motivating.
But if you burn out and quit as a result, you’ve lost the game.
Slow and steady is usually better.&lt;/p&gt;
&lt;h3 id="push-yourself"&gt;Push yourself&lt;/h3&gt;
&lt;p&gt;Since you are now consistent, the next step is to set goals, make plans and push yourself.
Muscles grow by adapting to stress via progressive overload.
The best way to induce stress is to increase the weight.
If you lift 100 kg today and your goal is to reach 140 kg in one year, then you must increase the weight by around 0.75 kg per week.
This is the outline of a long-term plan.&lt;/p&gt;
&lt;p&gt;There are several ways to drive progress.
Suppose you can comfortably lift 4 sets of 5 repetitions at 80 kg today.
Try 4x5 at 82.5 kg next week, then 4x5 at 85 kg, 4x5 at 87.5 kg, etc. until you plateau.
Don’t rush it—the idea is to start easy and gradually push yourself.
You can alternatively increase repetitions instead of weight: do 4x1 at 90 kg, then 4x2 at 90 kg, etc. until you can do 4x6 at 90 kg—then increment the weight to 100 kg and start over again with 4x1.
A third option is to combine weights and repetitions, e.g. 4x5 at 80 kg, 5x5 at 80 kg, 4x5 at 82.5 kg, 5x5 at 82.5 kg, etc.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/twenty_years_of_strength/2022_deadlift_262.gif" style="display: block; margin-left: auto; margin-right: auto; max-width: 368px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p style="text-align: center; font-style: italic;"&gt;Deadlifting 262.5 kg at 104 kg bodyweight in 2022.&lt;/p&gt;
&lt;h3 id="two-steps-forward-one-step-back"&gt;Two steps forward, one step back&lt;/h3&gt;
&lt;p&gt;When your strength plateaus—which it invariably will—drop the weight (or repetitions) and start over.
Suppose you lift 4x5 at 80 kg today, and 82.5 kg the next week, then 85 kg, etc. and eventually you fail at 95 kg.
What should you do next?
Try to drop down to 85 kg and start increasing by 2.5 kg per week again.
You’ll likely reach 100 kg this time.&lt;/p&gt;
&lt;p&gt;I take a big step back and start light in every new training program (every 8-12 weeks).
Some programs add a small step back every four weeks.
The “two steps forward, one step back’’ idea can be used at different resolutions in a long-term plan, and can be applied to weight, volume (total number of repetitions) and intensity (% of one-rep max).&lt;/p&gt;
&lt;h3 id="switch-it-up"&gt;Switch it up&lt;/h3&gt;
&lt;p&gt;Introduce slight variations to stay motivated and get unstuck when you stagnate.
If you train 5x5, try increasing the repetitions (e.g., 4x8) or decreasing them (e.g., 5x3).
Switch out barbell exercises for their dumbbell counterparts.
Experiment with unilateral alternatives, for instance heavy lunges instead of squats.
Train with bands or chains for a while.
But don’t experiment too much or too often—stick with something long enough for it to work.&lt;/p&gt;
&lt;h3 id="movements-not-muscles"&gt;Movements, not muscles&lt;/h3&gt;
&lt;p&gt;Want big arms?
Gyms are littered with men who feverishly curl with little to show for it.
But I have yet to see anyone with small arms bench three plates.&lt;/p&gt;
&lt;p&gt;The body responds well to heavy compound exercises: bench press, military press, rows, chin-ups, squats, deadlifts, etc.
These exercises train many muscles simultaneously and let you move serious weight.
I bench four plates, but never train arms directly.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/twenty_years_of_strength/2022_bench_182.gif" style="display: block; margin-left: auto; margin-right: auto; max-width: 368px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p style="text-align: center; font-style: italic;"&gt;Benching 182.5 kg at 104 kg bodyweight in 2022.&lt;/p&gt;
&lt;h3 id="compete-or-cooperate"&gt;Compete or cooperate&lt;/h3&gt;
&lt;p&gt;Compete with yourself.
Strength is easy to measure, so take advantage of it.
Test your strength, weigh yourself and take some pictures every three months.
Document your journey and make note of what works for you.&lt;/p&gt;
&lt;p&gt;Strength training can be solitary, but it does not have to be.
Train with others, build a community and share information.
Compete with your friends or sign up for a meet.
Read books and articles, participate in online forums, or publish a training log.&lt;/p&gt;
&lt;h3 id="food-makes-you-look-good"&gt;Food makes you look good&lt;/h3&gt;
&lt;p&gt;If your goal is to look good, then diet matters more than training.
I enjoy tweaking my training programs, but it honestly has no effect on how I look—only my diet does.&lt;/p&gt;
&lt;p&gt;To master your diet, start by writing down everything you eat for a week.
Buy a food scale and weigh your food.
Weigh yourself daily, but use a three-day average to assess progress.&lt;/p&gt;
&lt;p&gt;Once you’ve mapped out how you eat today, start adding or subtracting food.
It’s easier to control your diet if you eat the same meals every day, so lock them down if you can.
I fix all my meals except one per day, where I only impose a weight limit.
Don’t lose more than 0.5-1% of your bodyweight per week.
Don’t gain more than 0.25-0.5% per week.&lt;/p&gt;
&lt;p&gt;Healthy foods often have low caloric density, and calories are by far the most important factor.
Eat healthy most of the time.
“Healthy’’ means food your grandmother or a bodybuilder would approve of.
Eat enough protein.
Don’t drink calories.
Don’t do anything too extreme, like swearing off entire food groups, avoiding social gatherings or dropping weight sharply.
It rarely works out long term.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/twenty_years_of_strength/summer_2021_92kg.jpg" style="display: block; margin-left: auto; margin-right: auto; max-width: 400px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p style="text-align: center; font-style: italic;"&gt;At ~92 kg I look pretty good. At 110 kg? Not so much.&lt;/p&gt;
&lt;h3 id="mute-the-marketers"&gt;Mute the marketers&lt;/h3&gt;
&lt;p&gt;People will try to sell you supplements, clothes, gear and entertainment.
Buy if you like, but it’s mostly marketing designed to exploit your psychology.&lt;/p&gt;
&lt;p&gt;For instance, very few supplements have strong scientific backing.
Whey is a cheap source of protein and creatine has documented effects.
That’s about it.&lt;/p&gt;
&lt;p&gt;As for clothes and gear, buy a pair of Adidas lifting shoes, Rehband knee sleeves and a belt.
They will last a decade or more.
What kind of T-shirt you wear to the gym hardly matters.&lt;/p&gt;
&lt;p&gt;Fitness influencers peddle a mix of minutiae, drama and genuinely good information.
In the end, your attention is sold to their advertisers.
Use social media feeds with caution:
the information does not stick well and comparison is the thief of joy.
For information, read a few high-quality books.
For motivation, make some real-world friends.&lt;/p&gt;
&lt;h3 id="keep-learning"&gt;Keep learning&lt;/h3&gt;
&lt;p&gt;If you enjoyed this article, then 
&lt;a href="https://www.goodreads.com/en/book/show/25241855-the-art-of-lifting"&gt;The Art of Lifting&lt;/a&gt;
and 
&lt;a href="https://www.goodreads.com/book/show/25242034-the-science-of-lifting"&gt;The Science of Lifting&lt;/a&gt;
by Nuckols are short reads in the same spirit.
&lt;a href="https://www.goodreads.com/book/show/25049103-bigger-leaner-stronger"&gt;Bigger Leaner Stronger&lt;/a&gt;
by Matthews is an overall guide to training and nutrition.
The books by &lt;a href="https://www.goodreads.com/author/show/7136135.Eric_Helms"&gt;Eric Helms&lt;/a&gt; also fall into this category.
Rippetoe wrote three books I enjoyed: 
&lt;a href="https://www.goodreads.com/book/show/2098799.Starting_Strength"&gt;Starting Strength&lt;/a&gt;
covers technique on the main lifts, 
&lt;a href="https://www.goodreads.com/book/show/1677880.Practical_Programming_for_Strength_Training"&gt;Practical Programming&lt;/a&gt;
teaches strength program design and 
&lt;a href="https://www.goodreads.com/book/show/2543979.Strong_Enough_Thoughts_from_Thirty_Years_of_Barbell_Training"&gt;Strong Enough?&lt;/a&gt;
is a delightful collection of no-bullshit, down-to-earth essays.
&lt;a href="https://www.goodreads.com/author/show/2943483.Dan_John"&gt;Dan John&lt;/a&gt;
has also written many great essays.
&lt;a href="https://www.goodreads.com/book/show/40121378-atomic-habits"&gt;Atomic Habits&lt;/a&gt;
by Clear covers habit formation, which is a big part of training and lifestyle.&lt;/p&gt;
&lt;p&gt;Unsure where to start?
Set some goals based on the strength standards at
&lt;a href="https://exrx.net/Testing/WeightLifting/StrengthStandards"&gt;ExRx.net&lt;/a&gt;
or
&lt;a href="https://strengthlevel.com/strength-standards"&gt;strengthlevel.com&lt;/a&gt;.
Choose a program like Starting Strength, StrongLifts, 5/3/1, Texas Method, etc.
Good luck!&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 22 Jun 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-06-22:/articles/2026/ten-lessons-from-twenty-years-of-strength-training</guid><category>articles</category><category>strength</category><category>opinion</category></item><item><title>Optimal bus stop spacing</title><link>https://tommyodland.com/articles/2026/optimal-bus-stop-spacing</link><description>&lt;p&gt;Did you know that building new roads can &lt;em&gt;increase&lt;/em&gt; traffic congestion in a city?
It’s called &lt;a href="https://en.wikipedia.org/wiki/Braess%27s_paradox"&gt;Braess’s paradox&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a similar vein—though perhaps not as counterintuitive—adding bus stops to a bus route can increase travel time.
That’s the main takeaway from the article 
“&lt;a href="https://worksinprogress.co/issue/the-united-states-needs-fewer-bus-stops/"&gt;The United States needs fewer bus stops&lt;/a&gt;”.
It’s great when we can improve a system by subtracting from it.&lt;/p&gt;
&lt;p&gt;How can adding a stop increase travel time?
The reason is that travel time is a function of two competing mechanisms:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If there are many bus stops, you don’t have to walk far until you get to one&lt;/li&gt;
&lt;li&gt;However, with many bus stops, the bus will also have to stop many times on your journey&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="simulation"&gt;Simulation&lt;/h2&gt;
&lt;p&gt;Let us simulate this on a 10 kilometer stretch of road.
We assume that population density is uniform along the road, so a journey can be randomly generated by drawing 2 numbers; a start and an end.&lt;/p&gt;
&lt;p&gt;The figure below shows 25 random journeys, along with 6 equidistant bus stops 2000 meters apart.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/bus_stop_spacing/segments_bus_stops.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"/&gt;&lt;/p&gt;
&lt;p&gt;The figure above outlines the basic simulation setup.
We’ll also make the following assumptions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The bus has infinite passenger capacity and always stops at every bus stop.&lt;/li&gt;
&lt;li&gt;The bus dwells for 45 seconds at each bus stop.&lt;/li&gt;
&lt;li&gt;The bus has a speed of 25 kilometers per hour (slow, but realistic for a city).&lt;/li&gt;
&lt;li&gt;A person’s walking speed is 5 kilometers per hour.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of 25 journeys, let us look at one million journeys.
The figure below shows the distribution of travel time when traveling by foot and on the bus—a fine-grained histogram with a million journeys.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/bus_stop_spacing/journeys_6_bus_stops.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"/&gt;&lt;/p&gt;
&lt;p&gt;No journey ever takes more than two hours, since every person walks at a speed of 5 kilometers per hour, and the road is 10 kilometers long.
Most journeys are faster by bus, but this is not the case for very short journeys—in that case walking is faster.
The average travel time with the bus is 0.35 hours (21 minutes), which is significantly shorter than the average walking time of 0.67 hours.&lt;/p&gt;
&lt;p&gt;What if we add more bus stops? Does it help? Let us try with 16 bus stops.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/bus_stop_spacing/journeys_16_bus_stops.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"/&gt;&lt;/p&gt;
&lt;p&gt;With 16 bus stops the average spacing goes down to 667 meters.
The average travel time also goes down to 0.26 hours.&lt;/p&gt;
&lt;p&gt;What if we keep adding bus stops? Will travel time continue to decrease?
Let’s go all the way to 51 bus stops.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/bus_stop_spacing/journeys_51_bus_stops.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"/&gt;&lt;/p&gt;
&lt;p&gt;The figure above shows that more bus stops are not better, since with 51 bus stops the travel time is up to 0.36 hours.
In fact, a road with 51 bus stops is &lt;em&gt;worse&lt;/em&gt; than one with 6 bus stops!&lt;/p&gt;
&lt;p&gt;Here is the figure you might have been waiting for; the average travel time as a function of bus stops.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/bus_stop_spacing/travel_time_function_bus_stops.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"/&gt;&lt;/p&gt;
&lt;p&gt;The optimal number of bus stops is 16.
Does 16 stops—a spacing of 667 meters—seem reasonable?
I think so; you never have to walk for more than 333 meters to catch the bus, and on average you walk 167 meters.
This corresponds to about 4 and 2 minutes of walking, respectively.&lt;/p&gt;
&lt;h2 id="analytical-solution"&gt;Analytical solution&lt;/h2&gt;
&lt;p&gt;The bus stop problem is excellent because it can be approached either by computer simulation or analytical mathematics (deriving equations).
In fact, the two approaches complement each other nicely.&lt;/p&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(n\)&lt;/span&gt; be the number of bus stops and &lt;span class="math"&gt;\(L\)&lt;/span&gt; be the length of the road (10 kilometers). Then &lt;span class="math"&gt;\(\ell = L / (n-1)\)&lt;/span&gt; is the equidistant spacing between each bus stop.
Furthermore, let &lt;span class="math"&gt;\(v_w\)&lt;/span&gt; be the walking velocity and &lt;span class="math"&gt;\(v_b\)&lt;/span&gt; be the bus velocity.
Finally, we denote the dwell time of the bus, measured in hours, by &lt;span class="math"&gt;\(d\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The expected total travel time is the sum of three different terms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Starting at a random location, the average distance to the closest bus stop is &lt;span class="math"&gt;\(\ell / 4\)&lt;/span&gt;. We multiply this by two, since we have to walk both to the nearest bus stop at the start of our journey, and from the final bus stop at the end of our journey. The average time walking to and from the bus is therefore &lt;span class="math"&gt;\(\ell / (2 v_w)\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The average distance traveled on the bus is roughly &lt;span class="math"&gt;\(L / 3\)&lt;/span&gt;. The total time on the bus, excluding dwelling times, is therefore &lt;span class="math"&gt;\(L / (3 v_b)\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The number of stops on a journey of length &lt;span class="math"&gt;\(L/3\)&lt;/span&gt; is &lt;span class="math"&gt;\(L / (3 \ell)\)&lt;/span&gt;, so the total time on the bus due to dwelling at bus stops is &lt;span class="math"&gt;\(d L / (3 \ell)\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The skeptical reader is encouraged to verify the claims above more thoroughly.
If we put all this together, we obtain the average time &lt;span class="math"&gt;\(T\)&lt;/span&gt;, as a function of bus stops &lt;span class="math"&gt;\(n\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(n) &amp;amp;= \frac{\ell}{2 v_w} + \frac{L}{3 v_b} + \frac{d L}{3 \ell} \\
&amp;amp;= \frac{L}{(n-1) 2 v_w} + \frac{L}{3 v_b} + \frac{d (n-1)}{3}
\end{align*}&lt;/div&gt;
&lt;p&gt;
Differentiating and solving &lt;span class="math"&gt;\(T'(n^{\star}) = 0\)&lt;/span&gt;, we obtain&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
n^{\star} = 1 + \sqrt{\frac{3 L}{2 v_w d}}
\end{align*}&lt;/div&gt;
&lt;p&gt;This analytical equation matches the simulation results nicely, as shown in the figure below.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/bus_stop_spacing/travel_time_function_bus_stops_analytical.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 620px;
image-rendering: -webkit-optimize-contrast;"/&gt;&lt;/p&gt;
&lt;p&gt;Notice how the optimal number of bus stops grows like the square root of &lt;span class="math"&gt;\(L\)&lt;/span&gt;.
If the road segment becomes four times as long, only twice as many bus stops are needed.
This is because the average journey also becomes longer, due to the assumption that journeys are uniformly drawn from the line segment.&lt;/p&gt;
&lt;p&gt;Why the discrepancy between the simulations and the equation?
It’s because bus journeys were simulated by picking the fastest out of four possible journeys.
Given a starting point for a journey, a person can either go left or go right to catch the bus. 
Similarly, a person can either exit the bus before or after the final endpoint of the journey.
This gives four possible journeys, and in each simulation I computed the fastest one.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sat, 16 May 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-05-16:/articles/2026/optimal-bus-stop-spacing</guid><category>articles</category><category>mathematics</category></item><item><title>Deltidsstillinger</title><link>https://tommyodland.com/articles/2026/deltidsstillinger</link><description>&lt;p&gt;&lt;span class="caps"&gt;NRK&lt;/span&gt; har publisert en sak om &lt;a href="https://www.nrk.no/norge/mote-i-dag_-stenseng-varsler-nye-overtidsregler-for-deltidsansatte-1.17821037"&gt;overtidsregler for deltidsansatte&lt;/a&gt;,
og skriver at det er fastlåste posisjoner mellom arbeidsgivere (&lt;span class="caps"&gt;NHO&lt;/span&gt;, &lt;span class="caps"&gt;KS&lt;/span&gt;, Virke og Spekter) og arbeidstakere (Fagforbundet og &lt;span class="caps"&gt;LO&lt;/span&gt;).
I denne artikkelen presenterer vi et enkelt kompromiss.&lt;/p&gt;
&lt;h2 id="dagens-praksis-med-merarbeid-og-overtid"&gt;Dagens praksis med merarbeid og overtid&lt;/h2&gt;
&lt;p&gt;Anta at Ola jobber 50% stilling (20 timer per uke om vi antar en 40-timers uke) og tjener 300 kroner per time.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/deltidsstillinger/deltidsstillinger_dagens.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 600px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Hvis han jobber 10 timer over avtalt arbeidstid regnes dette som &lt;em&gt;merarbeid&lt;/em&gt;, ikke &lt;em&gt;overtid&lt;/em&gt;.
Han vil få utbetalt &lt;span class="math"&gt;\(300 \times (20 + 10) = 9000\)&lt;/span&gt; kroner.&lt;/p&gt;
&lt;p&gt;Om han derimot jobber 25 timer mer, jobber han 5 timer overtid (over grensa på 40 timer).
Dersom satsen for overtid er 40%, får han utbetalt &lt;span class="math"&gt;\(300 \times 40 + 300 \times 5 \times 1.4 = 14 100\)&lt;/span&gt; kroner.&lt;/p&gt;
&lt;h2 id="eu-domstolens-konklusjon"&gt;&lt;span class="caps"&gt;EU&lt;/span&gt;-domstolens konklusjon&lt;/h2&gt;
&lt;p&gt;I to dommer fra &lt;span class="caps"&gt;EU&lt;/span&gt;-domstolen regnes alt arbeid utover avtalt arbeidstid som overtid, ikke merarbeid.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/deltidsstillinger/deltidsstillinger_EU.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 600px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Med disse reglene vil Ola få følgende utbetalt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Om han jobber 10 timer over avtalt arbeidstid på 20 timer, får han &lt;span class="math"&gt;\(300 \times 20 + 300 \times 10 \times 1.4 = 10 200\)&lt;/span&gt; kroner.&lt;/li&gt;
&lt;li&gt;Om han jobber 25 timer over avtalt arbeidstid på 20 timer, får han &lt;span class="math"&gt;\(300 \times 20 + 300 \times 25 \times 1.4 = 16 500\)&lt;/span&gt; kroner.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="problemstillingen"&gt;Problemstillingen&lt;/h2&gt;
&lt;p&gt;I dag er det ingen strategisk grunn til at en bedrift bør ansette i 100% stilling.
Det er bedre å ha to ansatte i 50% stilling, fordi da har arbeidsgiver en viss fleksibilitet til å justere opp arbeidsmengden uten å betale ekstra.
Om bedriften trenger 50 arbeidstimer, vil det koste:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Med én ansatt: &lt;span class="math"&gt;\(300 \times 40 + 300 \times 10 \times 1.4 = 16 200\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Med to ansatte: &lt;span class="math"&gt;\(2 \times (300 \times 20 + 300 \times 5) = 15 000\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I regimet som &lt;span class="caps"&gt;EU&lt;/span&gt;-domstolen legger opp til, er det likegyldig om man har en eller to ansatte.
Prisen blir uansett:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Med én ansatt: &lt;span class="math"&gt;\(300 \times 40 + 300 \times 10 \times 1.4 = 16 200\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Med to ansatte: &lt;span class="math"&gt;\(2 \times (300 \times 20 + 300 \times 5 \times 1.4) = 16 200\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Det er også likegyldig hvem av de ansatte som jobber ekstra.
Dersom Ola har 60% stilling og Kari har 40% stilling, så har det ingen betydning for arbeidsgiver om det er Ola eller Kari som jobber ekstra.
Det er ingen gevinst i å balansere merarbeidet/overtiden.&lt;/p&gt;
&lt;h2 id="et-kompromiss"&gt;Et kompromiss&lt;/h2&gt;
&lt;p&gt;La funksjonen &lt;span class="math"&gt;\(p\)&lt;/span&gt; være overtidsprosent som funksjon av timer jobbet &lt;span class="math"&gt;\(x\)&lt;/span&gt;, og anta at stillingsprosenten (i timer) er gitt ved &lt;span class="math"&gt;\(s\)&lt;/span&gt;.
Et kompromiss er å velge en lineær funksjon &lt;span class="math"&gt;\(p(x) = ax + b\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/deltidsstillinger/deltidsstillinger_kompromiss.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 600px;
image-rendering: crisp-edges;"/&gt;
Dersom vi krever at &lt;span class="math"&gt;\(p(s) = 100\)&lt;/span&gt; og &lt;span class="math"&gt;\(p(40) = 140\)&lt;/span&gt;, så får vi&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
p(x;s) = \frac{40}{40 - s} (x - s) + 100
\end{equation*}&lt;/div&gt;
&lt;p&gt;
Når &lt;span class="math"&gt;\(x \leq s\)&lt;/span&gt; definerer vi &lt;span class="math"&gt;\(p(x;s) = 100\)&lt;/span&gt;, og når &lt;span class="math"&gt;\(x \geq 40\)&lt;/span&gt; definerer vi &lt;span class="math"&gt;\(p(x;s) = 140\)&lt;/span&gt;.
Dersom Ola jobber &lt;span class="math"&gt;\(t\)&lt;/span&gt; timer, skal han ha &lt;span class="math"&gt;\(300 \int_0^t (p(x) / 100) \, dx\)&lt;/span&gt; kroner i lønn.
For 30 timer får han 9 300 kroner, sammenlignet med 9 000 i dag og 10 200 under &lt;span class="caps"&gt;EU&lt;/span&gt;-løsningen.&lt;/p&gt;
&lt;p&gt;Partene vil neppe bli enige om et slikt kompromiss i praksis, men det har noen fordeler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Det koster alltid arbeidsgiver å planlegge dårlig og bruke ansatte utover avtalt stillingsprosent, enten de jobber deltid eller heltid.&lt;/li&gt;
&lt;li&gt;Arbeidsgiver har insentiv til å gi ansatte høyere stillingsprosent, heller enn å konsekvent bruke merarbeid/overtid.&lt;/li&gt;
&lt;li&gt;Arbeidsgiver betaler ikke like mye for å bruke ekstra arbeidskraft fra en ansatt i 50% stilling, som hvis samme arbeidskraft blir hentet fra en ansatt med 100% stilling. Hvem vil du helst bli operert av på sykehuset: fulltidslegen som er på tredje ekstravakt, eller deltidslegen som er på første ekstravakt?&lt;/li&gt;
&lt;li&gt;Dersom arbeidsgiver har to personer ansatt i 50% stilling og trenger to vakter ekstra, lønner det seg å fordele dem likt og gi én vakt til Ola og én til Kari. Med andre ord lønner det seg å balansere overtiden på de ansatte, i motsetning til både dagens regime og &lt;span class="caps"&gt;EU&lt;/span&gt;-domstolens tolkning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Du kan lese LOs argumenter
&lt;a href="https://www.lo.no/hva-vi-mener/et-likestilt-arbeidsliv/nyheter-om-likestilling/ofte-stilte-sp%C3%B8rsm%C3%A5l-om-overtid-p%C3%A5-deltid/"&gt;her&lt;/a&gt;
og noen motargumenter fra arbeidsgiverne
&lt;a href="https://e24.no/norsk-oekonomi/i/ny5d0o/vil-fagforbundet-svekke-ansattes-motivasjon-til-aa-jobbe-heltid"&gt;her&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Tue, 14 Apr 2026 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2026-04-14:/articles/2026/deltidsstillinger</guid><category>articles</category><category>mathematics</category></item><item><title>Continuous interest with continuous deposits</title><link>https://tommyodland.com/articles/2026/continuous-interest-with-continuous-deposits</link><description>&lt;p&gt;Writing a 
&lt;a href="https://tommyodland.com/tools/saving.html"&gt;savings calculator&lt;/a&gt;
is a great programming exercise for beginners.
They’re easy to implement in spreadsheets or programming languages like Python, and teach people about the power of compounding interest.
In this article we’ll investigate a discretization error that such calculators often make and try to fix it.&lt;/p&gt;
&lt;h2 id="a-simple-savings-calculator"&gt;A simple savings calculator&lt;/h2&gt;
&lt;p&gt;A simple savings calculator might look like this in Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;  &lt;span class="c1"&gt;# 10% annual interest&lt;/span&gt;
&lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;  &lt;span class="c1"&gt;# 10 units of money saved per year&lt;/span&gt;
&lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="n"&gt;saved&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;year&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;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;years&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;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;saved_per_year&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Year: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  Saved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Running the code produces the following output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Year:  1  Saved: 10.00
Year:  2  Saved: 21.00
Year:  3  Saved: 33.10
...
Year: 18  Saved: 455.99
Year: 19  Saved: 511.59
Year: 20  Saved: 572.75
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But there’s a simplification in this calculation which causes us to &lt;em&gt;underestimate&lt;/em&gt; the total amount saved.
Did you spot it?&lt;/p&gt;
&lt;h2 id="the-discretization-error"&gt;The discretization error&lt;/h2&gt;
&lt;p&gt;The “error” in the script above is noticeable in the first year of the computation, so let’s focus on the first year.
We assumed that one year passes before the amount saved per year is deposited into the account.
In reality, it would likely be deposited monthly as the person saves money throughout the year.&lt;/p&gt;
&lt;p&gt;The bank might pay you the interest owed monthly or yearly, but their internal computation is done &lt;em&gt;&lt;a href="https://money.stackexchange.com/a/10798"&gt;every single day&lt;/a&gt;&lt;/em&gt;.
Therefore depositing as early as possible (when you get your salary) will help accumulate more interest on your deposit throughout the year.
The same applies to mutual funds.&lt;/p&gt;
&lt;p&gt;If the yearly interest rate is &lt;span class="math"&gt;\(r\)&lt;/span&gt;, then the monthly interest rate is &lt;span class="math"&gt;\(r^{1/12}\)&lt;/span&gt; and the yearly interest rate is &lt;span class="math"&gt;\(r^{1/365}\)&lt;/span&gt;.
This is because interest rates compound multiplicatively, so the &lt;a href="https://en.wikipedia.org/wiki/Geometric_mean"&gt;geometric mean&lt;/a&gt;, not the arithmetic mean, is the correct computation.&lt;/p&gt;
&lt;p&gt;Back to our savings calculator: in that first year, if we deposit monthly and compute the interest monthly, we do not end up with &lt;span class="math"&gt;\(10\)&lt;/span&gt; units of money at the end of the first year.
Instead we end up with &lt;span class="math"&gt;\(10.45\)&lt;/span&gt; units of money:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;periods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
&lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.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;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;periods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;periods&lt;/span&gt;

&lt;span class="n"&gt;saved&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;period&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;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;periods&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;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;saved_per_year&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Period: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  Saved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;...
Period: 12  Saved: 10.45
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What happens if we compound daily (&lt;span class="math"&gt;\(365\)&lt;/span&gt; times) instead? Or hourly (&lt;span class="math"&gt;\(365 \times 12\)&lt;/span&gt; times)? Or every second (&lt;span class="math"&gt;\(365 \times 12 \times 3600\)&lt;/span&gt; times)?
Here is a figure showing the multiplicative factor that money grows by at the end of one year, as a function of the number of times we compound the interest, using &lt;span class="math"&gt;\(r=1.1\)&lt;/span&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/continuous_interest_and_deposits/interest_limit.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 650px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;For instance, if we compound &lt;span class="math"&gt;\(12\)&lt;/span&gt; times the multiplicative factor is &lt;span class="math"&gt;\(\approx 1.045\)&lt;/span&gt;.
Observe that the factor converges to a finite number instead of blowing up to infinity.
We’ll show that, as we compound interest rates and deposit more and more often, the factor converges to&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
(r - 1) / \ln (r),
\end{align*}&lt;/div&gt;
&lt;p&gt;
which for an interest rate &lt;span class="math"&gt;\(r=1.1\)&lt;/span&gt; becomes &lt;span class="math"&gt;\(0.1 / \ln(1.1) = 1.0492\)&lt;/span&gt; (compare with the figure above).&lt;/p&gt;
&lt;h2 id="continuous-deposits"&gt;Continuous deposits&lt;/h2&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(a\)&lt;/span&gt; be the total amount deposited in a year, let &lt;span class="math"&gt;\(r\)&lt;/span&gt; be the yearly interest rate and &lt;span class="math"&gt;\(n\)&lt;/span&gt; be the number of times interest is compounded.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The naïve savings calculator above used &lt;span class="math"&gt;\(n=1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;In terms of realism for a savings calculator, &lt;span class="math"&gt;\(n=12\)&lt;/span&gt; is a good choice if salary is paid every month and then deposited.&lt;/li&gt;
&lt;li&gt;From the bank’s perspective, whether it’s a bank account or a mutual fund, &lt;span class="math"&gt;\(n=365\)&lt;/span&gt; is the upper limit.&lt;/li&gt;
&lt;li&gt;We’re interested in what happens as &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt;. This is optimistic because it overestimates the discrete reality, but &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt; is still a much better approximation of &lt;span class="math"&gt;\(n=12\)&lt;/span&gt; than &lt;span class="math"&gt;\(n=1\)&lt;/span&gt; (again: see the figure).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(a\)&lt;/span&gt; be the yearly added value.
We can look for a pattern as we increase the number &lt;span class="math"&gt;\(n\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{n=1} \quad &amp;amp; a \\
\text{n=2} \quad &amp;amp; \left( \frac{a}{2} \right) r^{1/2} + \frac{a}{2} \\
\text{n=3} \quad &amp;amp; \left( \left( \frac{a}{3} \right) r^{1/3} + \frac{a}{3} \right) r^{1/3} + \frac{a}{3} \\
\text{n=4} \quad &amp;amp; \left( \left( \left( \left( \frac{a}{4} \right) r^{1/4} + \frac{a}{4} \right) \right) r^{1/4} + \frac{a}{4} \right) r^{1/4} + \frac{a}{4} \\
\end{align*}&lt;/div&gt;
&lt;p&gt;These are all &lt;a href="https://en.wikipedia.org/wiki/Geometric_series"&gt;geometric series&lt;/a&gt;, and can be written as:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{n=1} \quad &amp;amp; a \\
\text{n=2} \quad &amp;amp; \frac{a}{2} \left( r^{1/2} + 1 \right) \\
\text{n=3} \quad &amp;amp; \frac{a}{3} \left( r^{2/3} + r^{1/3} + 1 \right) \\
\text{n=4} \quad &amp;amp; \frac{a}{4} \left( r^{3/4} + r^{2/4} + r^{1/4} + 1 \right) \\
\end{align*}&lt;/div&gt;
&lt;p&gt;The pattern for a general &lt;span class="math"&gt;\(n\)&lt;/span&gt; emerges, and (provided we assume &lt;span class="math"&gt;\(r\neq 1\)&lt;/span&gt;) we can use the formula for a sum of a geometric series to obtain a closed-form expression:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f_n(a, r) = \frac{a}{n} \sum_{i=0}^{n-1} \left( r^{1/n} \right)^i = \frac{a}{n} \frac{\left( r^{1/n} \right)^n - 1}{r^{1/n} - 1} = \frac{a}{n} \frac{r - 1}{r^{1/n} - 1}
\end{align*}&lt;/div&gt;
&lt;p&gt;What does this converge to as &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt;?
It all comes down to what the denominator &lt;span class="math"&gt;\(n (r^{1/n} - 1)\)&lt;/span&gt;, since that’s the only part of the equation that depends on &lt;span class="math"&gt;\(n\)&lt;/span&gt;.
The answer is &lt;span class="math"&gt;\(\ln(r)\)&lt;/span&gt;, and there are many ways to show this: L’Hôpital’s Rule and Taylor Series are both possible methods.
Here’s one way that I like: one of the many definitions of the exponential function &lt;span class="math"&gt;\(e^x\)&lt;/span&gt; is that
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
e^x = \lim_{n \to \infty} \left(1+\frac{x}{n}\right)^n
\end{align*}&lt;/div&gt;
&lt;p&gt;
so then, since &lt;span class="math"&gt;\(\ln(x)\)&lt;/span&gt; is the inverse of &lt;span class="math"&gt;\(e^x\)&lt;/span&gt; for positive &lt;span class="math"&gt;\(x\)&lt;/span&gt;,  we must have
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
x = e^{\ln (x)} = \lim_{n \to \infty} \left(1+\frac{\ln (x)}{n}\right)^n
\end{align*}&lt;/div&gt;
&lt;p&gt;
and if we rearrange this expression and substitute &lt;span class="math"&gt;\(x\)&lt;/span&gt; for &lt;span class="math"&gt;\(r\)&lt;/span&gt;, we discover that &lt;span class="math"&gt;\(\lim_{n \to \infty} n (r^{1/n} - 1) = \ln(r)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The final result is that
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\lim_{n \to \infty} f_n(a, r) = \lim_{n \to \infty} \frac{a(r - 1)}{n (r^{1/n} - 1)} = \frac{a (r - 1)}{\ln (r)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;We’re now in a position to create a more realistic savings calculator, in which money is continually deposited and interest is continually accrued:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;

&lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;
&lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="n"&gt;saved&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;year&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;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;years&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;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;interest_rate&lt;/span&gt;
    &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interest_rate&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="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interest_rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Year: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  Saved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Year:  1  Saved: 10.49
Year:  2  Saved: 22.03
Year:  3  Saved: 34.73
...
Year: 18  Saved: 478.43
Year: 19  Saved: 536.76
Year: 20  Saved: 600.93
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The difference is shown graphically in the animated figure below.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/continuous_interest_and_deposits/discrete_vs_continuous.gif" style="display: block; margin-left: auto; margin-right: auto; max-width: 650px; width: 95%;"/&gt;&lt;/p&gt;
&lt;h3 id="solution-by-integration"&gt;Solution by integration&lt;/h3&gt;
&lt;p&gt;We can obtain the same answer as above by realizing that as &lt;span class="math"&gt;\(n\)&lt;/span&gt; becomes large we can replace the sum with a &lt;a href="https://en.wikipedia.org/wiki/Riemann_integral"&gt;Riemann integral&lt;/a&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f_n(a, r) = \frac{a}{n} \sum_{i=0}^{n-1} r^{i/n} \approx a \int_{0}^{1} r^{x} \, dx
\end{align*}&lt;/div&gt;
&lt;p&gt;
If you know the indefinite integral of &lt;span class="math"&gt;\(r^x\)&lt;/span&gt;, then it’s easy to compute
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\int_{0}^{1} r^{x} \, dx = \int_{0}^{1} \exp(x \ln(r)) \, dx = \frac{1}{\ln(r)} \exp(x \ln(r)) \Biggr|^{x = 0}_{x = 1} = \frac{r-1}{\ln(r)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
It is comforting that both approaches give identical results.
Let’s look at a third approach that also leads to the same answer, but is more general and more powerful: differential equations.&lt;/p&gt;
&lt;h2 id="solution-by-differential-equations"&gt;Solution by differential equations&lt;/h2&gt;
&lt;p&gt;Let us summarize what we have learned so far.
When we account for continuous deposits and continuous interest rates, we do not have &lt;span class="math"&gt;\(a\)&lt;/span&gt; units of money after the first year.
Instead we end up with &lt;span class="math"&gt;\(a (r-1) / \ln(r)\)&lt;/span&gt; units of money.
The factor &lt;span class="math"&gt;\((r-1) / \ln(r)\)&lt;/span&gt; accounts for the difference between discrete yearly deposits and continuous deposits throughout the year. When &lt;span class="math"&gt;\(r=1\)&lt;/span&gt; this factor is undefined, but if we assume &lt;span class="math"&gt;\(r \neq 1\)&lt;/span&gt; then we get this graph:&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/continuous_interest_and_deposits/corretion_function.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 650px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;In the figure above, I also plotted the first-order Taylor expansion around &lt;span class="math"&gt;\(r \approx 1\)&lt;/span&gt;, which is &lt;span class="math"&gt;\(g(r) = 1/2 + r/2\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The factor that “fixes” the first year, but can be applied every single year in our savings calculator.
If we re-write the recursive definition into a sum, and again use the sum of a geometric series, we end up with an expression for an arbitrary year &lt;span class="math"&gt;\(n\)&lt;/span&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{n=1} \quad &amp;amp; a \frac{r-1}{\ln(r)} \\
\text{n=2} \quad &amp;amp; \left( a \frac{r-1}{\ln(r)} \right) r + a \frac{r-1}{\ln(r)} \\
\text{n=3} \quad &amp;amp; \left( \left( a \frac{r-1}{\ln(r)} \right) r + a \frac{r-1}{\ln(r)} \right) r + a \frac{r-1}{\ln(r)} \\
\text{n} \quad &amp;amp; a \frac{r-1}{\ln(r)} \left( r^{n-1} + r^{n-2} + \cdots + 1  \right) = a \frac{r-1}{\ln(r)} \frac{r^n - 1}{r-1} = a \frac{r^n - 1}{\ln(r)}
\end{align*}&lt;/div&gt;
&lt;p&gt;Perhaps the most powerful approach to studying continuous deposits and continuous interest rates is by using differential equations. Let &lt;span class="math"&gt;\(M(t)\)&lt;/span&gt; be money as a function of time, which is now a continuous function of &lt;span class="math"&gt;\(t\)&lt;/span&gt;. Let &lt;span class="math"&gt;\(r\)&lt;/span&gt; be the yearly interest rate and &lt;span class="math"&gt;\(t\)&lt;/span&gt; be measured in years.
Before we proceed to the general case, let us study two simple cases first:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deposits without any interest.&lt;/strong&gt;
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
M(t) = at \qquad M'(t)= a
\end{align*}&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Interest without any deposits.&lt;/strong&gt;
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
M(t) = M_0 r^t \qquad M'(t) = \ln(r)  M_0 r^t = \ln(r) M(t)
\end{align*}&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Both deposits and interest.&lt;/strong&gt;
Above we saw that the rate of change in a bank account is driven by &lt;span class="math"&gt;\(a\)&lt;/span&gt; as well as &lt;span class="math"&gt;\(\ln(r) M(t)\)&lt;/span&gt;.
We sum these to obtain the (first order linear ordinary) differential equation&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
M'(t) = \ln(r) M(t) + a.
\end{align*}&lt;/div&gt;
&lt;p&gt;The solution to this differential equation (verify it!) is&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
M(t) = M_0 r^t + \frac{a (r^t - 1)}{\ln(r)},
\end{align*}&lt;/div&gt;
&lt;p&gt;
where &lt;span class="math"&gt;\(M_0 = M(0)\)&lt;/span&gt; is the initial money in the account.
This matches our previous result perfectly, where we assumed &lt;span class="math"&gt;\(M_0 = 0\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;As a final note, if &lt;span class="math"&gt;\(a\)&lt;/span&gt; is negative and we’re depleting the account, then a relevant question is “When will we go broke?”.
This amounts to solving &lt;span class="math"&gt;\(M(t) = 0\)&lt;/span&gt; for an unknown &lt;span class="math"&gt;\(t\)&lt;/span&gt;.
In the general case, let &lt;span class="math"&gt;\(M(t) = M_t\)&lt;/span&gt;, then we can solve for &lt;span class="math"&gt;\(t\)&lt;/span&gt; to obtain:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
t = \frac{1}{\ln (r)} \ln \left( \frac{M_t \ln(r) + a}{M_0 \ln(r) + a} \right)
\end{align*}&lt;/div&gt;
&lt;p&gt;Finally, here is an updated Python snippet that uses the function &lt;span class="math"&gt;\(M(t)\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;M&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;M0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r_power_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&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="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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;M0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r_power_t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r_power_t&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="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;
&lt;span class="n"&gt;saved_per_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;year&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;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;years&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;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&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;interest_rate&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;saved_per_year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;M0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Year: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  Saved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="summary-and-references"&gt;Summary and references&lt;/h2&gt;
&lt;p&gt;This is an example of how the first thing on​e might implement is not necessarily correct.
I’ve taught students about geometric series, Excel and Python using this example a few times, and I was aware of the tension between computing the interest and deposit &lt;em&gt;at the end of the year&lt;/em&gt; (which underestimates) and &lt;em&gt;at the start of the year&lt;/em&gt; (which overestimates).&lt;/p&gt;
&lt;p&gt;For students familiar with calculus and geometric series, working out the limit of &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt; is a nice problem.
In the end, assuming &lt;span class="math"&gt;\(n \to \infty\)&lt;/span&gt; is not really correct either—it probably overestimates slightly compared to the reality (which is likely &lt;span class="math"&gt;\(n=12\)&lt;/span&gt; or so).
In many cases the corrective factor of &lt;span class="math"&gt;\((r - 1) / \ln (r)\)&lt;/span&gt; is likely worth implementing since it’s closer to the truth than the naive &lt;span class="math"&gt;\(n=1\)&lt;/span&gt; computation.
We ignored most issues related to numerical stability in this article, but in a proper implementation &lt;span class="math"&gt;\(r^{(1/n)}\)&lt;/span&gt; and &lt;span class="math"&gt;\((r-1) / \ln(r)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Everything can be summarized by this set of equations: one discrete &lt;a href="https://en.wikipedia.org/wiki/Recurrence_relation#difference_equation"&gt;difference equation&lt;/a&gt; and one continuous differential equation.
The equations and their closed-form solutions are:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boxed{M_n = r M_{n-1} + a} \Rightarrow M_n &amp;amp;= M_0 r^n + a \frac{r^n - 1}{r - 1} \\
\boxed{M'(t) = \ln(r) M(t) + a} \Rightarrow M(t) &amp;amp;= M_0 r^t + a \frac{r^t - 1}{\ln(r)}
\end{align*}&lt;/div&gt;
&lt;p&gt;Some further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.njit.edu/~bose/interest-chapter.pdf"&gt;Modeling Change One Step at a Time&lt;/a&gt;, from &lt;a href="https://www.jstor.org/stable/j.ctt1bw1hh8"&gt;Topics in Mathematical Modeling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://math.libretexts.org/Bookshelves/Differential_Equations/Differential_Equations_(Chasnov)/03:_First-Order_ODEs/3.04:_Applications"&gt;Differential Equations&lt;/a&gt; by Chasnov&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Continuous-repayment_mortgage"&gt;Continuous-repayment mortgage&lt;/a&gt; on Wikipedia&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Compound_interest"&gt;Compound interest&lt;/a&gt; on Wikipedia&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Wed, 25 Mar 2026 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2026-03-25:/articles/2026/continuous-interest-with-continuous-deposits</guid><category>articles</category><category>mathematics</category></item><item><title>Skatter og bidrag</title><link>https://tommyodland.com/articles/2026/skatter-og-bidrag</link><description>&lt;p&gt;Denne artikkelen er et tankeeksperiment der vi skal endre det norske skatte- og bidragssystemet.
I dag styrer mange mekanismer pengeflyten mellom borgere og staten.
Et forenklet bilde ser slik ut:&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/simple_taxes_full.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 600px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Kompleksiteten i et slikt system har store kostnader.
Staten må holde styr på alle skattene og bidragsordningene: den må oppdatere lovverk, endre satser, vurdere og innvilge søknader, vedlikeholde digital infrastruktur, osv.
Borgere må forstå systemet, være informert om støtteordningene og navigere søknadsprosesser.
Begge parter kunne vært tjent med en forenkling.&lt;/p&gt;
&lt;p&gt;Vi skal kutte nesten alle skatter og samle dem sammen i en funksjon &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;.
Deretter skal vi kutte nesten alle støtteordninger og samle dem sammen i en funksjon &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = \frac{H(e^{-kx} + kx - 1)}{k} \qquad B(x) = be^{-x/b}
\end{align*}&lt;/div&gt;
&lt;p&gt;Det er ikke åpenbart at disse funksjonene er i stand til å modellere store deler av forholdet mellom staten og borgerne.
Vi skal prøve likevel.
Resultatet blir at forholdet mellom staten og individet forenkles til følgende diagram.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/simple_taxes_simplified.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 410px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Alle skatter og bidrag styres av tre parametre &lt;span class="math"&gt;\(H\)&lt;/span&gt;, &lt;span class="math"&gt;\(k\)&lt;/span&gt; og &lt;span class="math"&gt;\(b\)&lt;/span&gt;, og vi skal estimere disse.
Underveis skal vi gi skattelette til folk flest, “ta de rike”, forenkle offentlig sektor, sørge for at det alltid lønner seg å jobbe, og kreve formuesskatt fra alle borgere.
Så her er det noe for alle, uansett politisk ståsted.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#inntekt"&gt;Inntekt&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#matematiske-egenskaper"&gt;Matematiske egenskaper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#hva-kan-vi-forenkle"&gt;Hva kan vi forenkle?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#vanlige-folks-tur"&gt;Vanlige folks tur&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#oppsummering-inntekt"&gt;Oppsummering: Inntekt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#bidrag-og-sttteordninger"&gt;Bidrag og støtteordninger&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#a-fange-de-fattige"&gt;Å fange de fattige&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#bidrag-bx-med-gode-egenskaper"&gt;Bidrag \(B(x)\) med gode egenskaper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#hva-kan-vi-forenkle_1"&gt;Hva kan vi forenkle?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#en-realistisk-beregning"&gt;En realistisk beregning&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#oppsummering-bidrag-og-sttteordninger"&gt;Oppsummering: Bidrag og støtteordninger&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#formuesskatt"&gt;Formuesskatt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#oppsummering"&gt;Oppsummering&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#referanser-og-videre-lesing"&gt;Referanser og videre lesing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#appendiks-a-bevis-for-at-bidragsfunksjonen-har-gode-insentiver"&gt;Appendiks A: Bevis for at bidragsfunksjonen har gode insentiver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#appendiks-b-formuesskatten"&gt;Appendiks B: Formuesskatten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#appendiks-c-kode"&gt;Appendiks C: Kode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h1 id="inntekt"&gt;Inntekt&lt;/h1&gt;
&lt;p&gt;For å regne ut skatt på en vanlig borgers inntekt må man ta hensyn til trygdeavgift, minstefradrag, personfradrag, skatt på alminnelig inntekt og trinnskatt.
Dette er et komplisert regnestykke, og i artikkelen &lt;a href="https://tommyodland.com/articles/2024/smooth-taxes-without-brackets"&gt;Smooth taxes without brackets&lt;/a&gt; viste jeg at beregningen kan reduseres til formelen&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = 
\frac{H(e^{-kx} + kx - 1)}{k},
\end{align*}&lt;/div&gt;
&lt;p&gt;der &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; er skatten man betaler om man har en brutto inntekt på &lt;span class="math"&gt;\(x\)&lt;/span&gt; kroner. Parametrene &lt;span class="math"&gt;\(H\)&lt;/span&gt; og &lt;span class="math"&gt;\(k\)&lt;/span&gt; bestemmer hvor aggressiv skatten er.
&lt;span class="math"&gt;\(H\)&lt;/span&gt; er den maksimale marginalskatten ettersom &lt;span class="math"&gt;\(H = \lim_{x \to \infty} T'(x)\)&lt;/span&gt;.
Figuren nedenfor viser at funksjonen er en meget god tilpasning til dagens inntektsskatt.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/taxes_2025_fit.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;I figuren ovenfor viser vi også inntektsfordelingen, som er hentet fra &lt;a href="https://www.ssb.no/arbeid-og-lonn/lonn-og-arbeidskraftkostnader/artikler/hvor-stort-er-egentlig-lonnsgapet-mellom-kvinner-og-menn"&gt;figur 2 i denne &lt;span class="caps"&gt;SSB&lt;/span&gt;-artikkelen&lt;/a&gt;.
Boks 2.2 i &lt;a href="https://www.regjeringen.no/no/dokumenter/prop.-1-ls-20242025/id3057469/"&gt;Skatter og avgifter 2025&lt;/a&gt; viser statens egen visualisering av marginalskatten.
&lt;a href="https://www.smartepenger.no/105-kalkulator/3798-skatteberegning-2025"&gt;Denne skattekalkulatoren&lt;/a&gt; har 27 felter som kan fylles ut, så den fulle beregningen er enda mer komplisert.&lt;/p&gt;
&lt;h2 id="matematiske-egenskaper"&gt;Matematiske egenskaper&lt;/h2&gt;
&lt;p&gt;Her kommer en rask oppsummering av hvilke spørsmål funksjonen &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; svarer på og hvilke egenskaper som er hensiktsmessige for en slik skattefunksjon.
Se &lt;a href="https://tommyodland.com/articles/2024/smooth-taxes-without-brackets"&gt;Smooth taxes without brackets&lt;/a&gt; for en grundigere gjennomgang.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nettoinntekt:&lt;/strong&gt; &lt;span class="math"&gt;\(x - T(x)\)&lt;/span&gt; svarer på spørsmålet “Hvor mye penger har jeg igjen etter at jeg har betalt skatt?”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Marginalskatt:&lt;/strong&gt; &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; svarer på “Hvor stor andel må jeg skatte på neste krone jeg tjener?”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skatteprosent:&lt;/strong&gt; &lt;span class="math"&gt;\(T(x) / x\)&lt;/span&gt; svarer på “Hvilken andel av total inntekt betaler jeg i skatt?”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Eksempel.&lt;/strong&gt; Dersom &lt;span class="math"&gt;\(x=800 \text{ TNOK}\)&lt;/span&gt;, så er:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = 227 \text{ TNOK} \quad 
T'(x) = 0.423 \quad
T(x)/x = 0.284
\end{align*}&lt;/div&gt;
&lt;p&gt;Funksjonen &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; har to viktige egenskaper bakt inn i definisjonen, for all verdier av &lt;span class="math"&gt;\(0 &amp;lt; H \leq 1\)&lt;/span&gt; og &lt;span class="math"&gt;\(k &amp;gt; 0\)&lt;/span&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gode insentiver.&lt;/strong&gt; En skatt &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; har gode insentiver dersom marginalskatten &lt;span class="math"&gt;\(T'(x) &amp;lt; 1\)&lt;/span&gt; for alle &lt;span class="math"&gt;\(x\)&lt;/span&gt;, fordi da ender man alltid opp med økt nettoinntekt dersom man tjener mer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progressiv beskatning.&lt;/strong&gt; En skatt &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; er progressiv dersom skatteprosenten &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; øker når &lt;span class="math"&gt;\(x\)&lt;/span&gt; øker. Dersom Ola tjener mer enn Kari, så betaler Ola også en større prosentandel av sin inntekt i skatt.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hva-kan-vi-forenkle"&gt;Hva kan vi forenkle?&lt;/h2&gt;
&lt;p&gt;Fra en borgers perspektiv er kun skattenivået relevant, ikke hva staten kaller de ulike skattene og fradragene eller hva som finansierer hva.
Dersom vi bruker &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; kan vi ta bort:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Minstefradrag&lt;/li&gt;
&lt;li&gt;Personfradrag&lt;/li&gt;
&lt;li&gt;Skatt på alminnelig inntekt&lt;/li&gt;
&lt;li&gt;Trinnskatt&lt;/li&gt;
&lt;li&gt;Arbeidsgiveravgift (det er i praksis en personskatt på 14.1 %)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La oss også samle all inntekt i &lt;span class="math"&gt;\(x\)&lt;/span&gt;: pensjonsgivende inntekt, utbytte fra selskaper, aksjegevinst, styrehonorarer, osv.
De som eier et selskap kan i dag velge å enten ta ut penger som inntekt eller som utbytte.
Inntekt gir &lt;a href="https://www.azets.com/no-no/innsikt/artikler/lonn-eller-utbytte"&gt;pensjonssparing gjennom folketrygden og rett til sykepenger&lt;/a&gt;, men utbytte har lavere marginalskatt.
Det er ofte optimalt å først maksimere pensjonssparing ved å ta ut lønn opp til 7.1 G og deretter ta utbytte.
Denne typen optimalisering er kun tilgjengelig for et fåtall.
Jo enklere skatten er, desto mindre muligheter er det for optimalisering.&lt;/p&gt;
&lt;p&gt;Om vi er villige til å fjerne noen flere, mer målrettede insentivordninger, kan vi fjerne:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Soner og næringsgrupperinger i arbeidsgiveravgift (&lt;a href="https://www.skatteetaten.no/satser/arbeidsgiveravgift/"&gt;14 koeffisienter&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;span class="caps"&gt;BSU&lt;/span&gt; fradrag&lt;/li&gt;
&lt;li&gt;Fagforeningskontingent&lt;/li&gt;
&lt;li&gt;Foreldrefradrag&lt;/li&gt;
&lt;li&gt;Reiseutgifter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Noen av disse fradragene bør modelleres som bidrag, og vi kan legge dem tilbake senere.&lt;/p&gt;
&lt;h2 id="vanlige-folks-tur"&gt;Vanlige folks tur&lt;/h2&gt;
&lt;p&gt;Vi kan gi skattelette til store deler av befolkningen om vi velger en høyere verdi for maksimal marginalskatt &lt;span class="math"&gt;\(H\)&lt;/span&gt; og løser for &lt;span class="math"&gt;\(k\)&lt;/span&gt; slik at statens inntekter forblir identiske med dagens nivå (&lt;span class="math"&gt;\(H \approx 0.47\)&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/equivalent_taxes.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Medianpersonen har en inntekt på omtrent 640 &lt;span class="caps"&gt;TNOK&lt;/span&gt; før skatt.
Går vi fra dagens skatteregime til &lt;span class="math"&gt;\(H=0.55\)&lt;/span&gt;, vil medianpersonen få skattelette på omtrent 3.8 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år.
Setter vi heller &lt;span class="math"&gt;\(H=0.65\)&lt;/span&gt; får medianpersonen skattelette på 7.4 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år.
Alle som er under 80 persentilen vil få skattelette, mens de som er over må skatte mer.
I dagens system har &lt;span class="caps"&gt;DNB&lt;/span&gt;-sjefen, som &lt;a href="https://e24.no/boers-og-finans/i/73gvzV/dnb-sjef-kjerstin-braathen-tjente-169-millioner-i-fjor"&gt;tjente 16.9 &lt;span class="caps"&gt;MNOK&lt;/span&gt; i fjor&lt;/a&gt;, samme marginalskatt som en dyktig ingeniør (eller lege, advokat, osv.) som tjener en tiendedel av dette.&lt;/p&gt;
&lt;p&gt;Tabellen sammenligner dagens scenario med to alternativer.
Statens inntekter er identiske.&lt;/p&gt;
&lt;table class="table-striped table-hover table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;                             &lt;/th&gt;
&lt;th&gt;&lt;span class="math"&gt;\(H=0.474\)&lt;/span&gt;  &lt;/th&gt;
&lt;th&gt;&lt;span class="math"&gt;\(H=0.55\)&lt;/span&gt;  &lt;/th&gt;
&lt;th&gt;&lt;span class="math"&gt;\(H=0.65\)&lt;/span&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Skattelette for medianperson&lt;/td&gt;
&lt;td&gt;0 &lt;span class="caps"&gt;NOK&lt;/span&gt;      &lt;/td&gt;
&lt;td&gt; 3800 &lt;span class="caps"&gt;NOK&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;7400 &lt;span class="caps"&gt;NOK&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Andel som får skattelette    &lt;/td&gt;
&lt;td&gt;0 %        &lt;/td&gt;
&lt;td&gt; 79 %     &lt;/td&gt;
&lt;td&gt;80 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marginalskatt &lt;span class="caps"&gt;DNB&lt;/span&gt;-sjefen    &lt;/td&gt;
&lt;td&gt;47 %      &lt;/td&gt;
&lt;td&gt; 55 %     &lt;/td&gt;
&lt;td&gt;65 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marginalskatt ingeniør      &lt;/td&gt;
&lt;td&gt;47 %    &lt;/td&gt;
&lt;td&gt; 53 %     &lt;/td&gt;
&lt;td&gt;60 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skatteprosent &lt;span class="caps"&gt;DNB&lt;/span&gt;-sjefen    &lt;/td&gt;
&lt;td&gt;46 %      &lt;/td&gt;
&lt;td&gt; 53 %     &lt;/td&gt;
&lt;td&gt;63 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skatteprosent ingeniør      &lt;/td&gt;
&lt;td&gt;37 %      &lt;/td&gt;
&lt;td&gt; 40 %     &lt;/td&gt;
&lt;td&gt;43 %     &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="oppsummering-inntekt"&gt;Oppsummering: Inntekt&lt;/h2&gt;
&lt;p&gt;Vi samler alt en person tjener i én felles inntekt &lt;span class="math"&gt;\(x\)&lt;/span&gt; og beskatter den med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;, enten det er ordinær pensjonsgivende inntekt, utbytte fra selskaper, avkastning på aksjefond, styrehonorar, godtgjørelser eller annet.
Da vil en rik investor med 10 &lt;span class="caps"&gt;MNOK&lt;/span&gt; i aksjegevinst skatte en høyere andel enn en småsparer, noe som ikke er tilfellet i dag.
Mange muligheter for optimalisering forsvinner, og de som får inntekt gjennom utbytte, avkastning og honorarer får samme beregning som folk flest.
Ikke minst blir alt mye enklere.
Vi kan endre &lt;span class="math"&gt;\(H\)&lt;/span&gt; og gi skattelette til 80 % av arbeidsstyrken, men insentivene må også vurderes.&lt;/p&gt;
&lt;p&gt;Vi kan også inkludere arv i &lt;span class="math"&gt;\(x\)&lt;/span&gt;, ettersom det også er inntekt.
Det ville vært en dobbeltbeskatning av &lt;em&gt;penger&lt;/em&gt;, men ikke en dobbeltbeskatning av &lt;em&gt;personer&lt;/em&gt;.
Dersom man arver en villa med nedbetalt lån i Oslo med verdi 20 &lt;span class="caps"&gt;MNOK&lt;/span&gt;, ville man måtte låne 9.1 &lt;span class="caps"&gt;MNOK&lt;/span&gt; i boligen for å betale skatten.
Dette ville utjevnet forskjeller og forhindret opphopning av kapital.
Alternativt kan vi droppe arveskatt og heller endre formuesskatten, noe vi kommer til senere i artikkelen.&lt;/p&gt;
&lt;h1 id="bidrag-og-sttteordninger"&gt;Bidrag og støtteordninger&lt;/h1&gt;
&lt;p&gt;Det eksisterer mange støtteordninger for mennesker som av ulik grunn har lav inntekt, enten i en periode eller permanent.
&lt;span class="caps"&gt;NAV&lt;/span&gt; har &lt;a href="https://www.nav.no/tjenester"&gt;over 200 tjenester&lt;/a&gt;, og noen av disse er:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uføretrygd&lt;/li&gt;
&lt;li&gt;Sykepenger&lt;/li&gt;
&lt;li&gt;Arbeidsavklaringspenger (&lt;span class="caps"&gt;AAP&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;Dagpenger&lt;/li&gt;
&lt;li&gt;Tiltakspenger&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I disse fem kategoriene ble det utbetalt &lt;a href="https://www.nav.no/no/lokalt/oslo/nyheter/8-000-flere-fikk-penger-fra-nav-i-2024"&gt;27.3 milliarder &lt;span class="caps"&gt;NOK&lt;/span&gt;&lt;/a&gt; til 166 000 Oslo-borgere i 2024, noe som tilsvarer 164 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per person.
I noen bydeler er andelen som mottar utbetalinger relatert til arbeidsliv og sykdom over 40 %, og på landsbasis er den 36 %.
Andre støtteordninger inkluderer pleiepenger, omsorgspenger, svangerskapspenger, yrkesskadeerstatning, foreldrepenger og økonomisk sosialhjelp.&lt;/p&gt;
&lt;p&gt;Kritikk mot &lt;span class="caps"&gt;NAV&lt;/span&gt; går ofte på at dagens system:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Er krevende å navigere&lt;/li&gt;
&lt;li&gt;Har dårlige insentiver&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Systemet blir lettere å navigere om vi fjerner kompleksitet.
Problemstillingen med insentiver kan forstås ved å se på enkeltsaker som viser såkalte &lt;em&gt;bidragsfeller&lt;/em&gt; (&lt;a href="https://en.wikipedia.org/wiki/Welfare_trap"&gt;welfare traps&lt;/a&gt;).
I noen tilfeller lønner det seg ikke å jobbe fordi da forsvinner bidragene:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/ytring/straffen-for-a-jobbe-1.17128172"&gt;Straffen for å jobbe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/nordland/ufore-jan-erik-fortviler_-_-taper-nesten-1000-kroner-i-maneden-pa-a-jobbe-1.17346914"&gt;Uføre Jan-Erik fortviler: - Taper nesten 1000 kroner i måneden på å jobbe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/valg2013/_tjener_-mer-pa-nav-enn-pa-jobb-1.11200902"&gt;&lt;span class="dquo"&gt;“&lt;/span&gt;Tjener” mer på &lt;span class="caps"&gt;NAV&lt;/span&gt; enn på jobb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/trondelag/uforetrygd-mer-lonnsomt-enn-jobb-1.8185736"&gt;Uføretrygd mer lønnsomt enn jobb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NAVs egen rapport fra 2023 anslår at &lt;a href="https://arbeidogvelferd.nav.no/news/2023/11/1%20av%204%20dagpengemottakere%20kan%20tape%20p%C3%A5%20%C3%A5%20jobbe%20%C3%A9n%20dag"&gt;1 av 4 dagpengemottakere kan tape på å jobbe én dag&lt;/a&gt;.
Mange mennesker lever i økonomiske bidragsfeller.&lt;/p&gt;
&lt;h2 id="a-fange-de-fattige"&gt;Å fange de fattige&lt;/h2&gt;
&lt;p&gt;Det er fort gjort å konstruere en bidragsfelle. 
Intensjonene må være så gode at man ikke tenker nøye nok over insentivene.
Dersom bidraget er betinget på lønn (enten direkte eller indirekte via arbeidsevne) og kuttes skarpt når inntekt øker, så sitter man i fella.
Dersom bidraget derimot senkes sakte, så unngår man feller.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eksempel - konstant bidragsfelle.&lt;/strong&gt;
Hva om vi gir alle som tjener mindre enn 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt; en utbetaling på 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt;?
Anta at vi har fem personer med ulik inntekt. La &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; være et bidrag som funksjon av bruttoinntekt &lt;span class="math"&gt;\(x\)&lt;/span&gt;.
Vi setter &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; lik 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt; dersom &lt;span class="math"&gt;\(x\)&lt;/span&gt; er mindre enn eller lik 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt;, og null hvis ikke.
Visualiseringen viser hva som skjer:&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/welfare_trap_constant.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Når man overstiger 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt; i inntekt, så forsvinner hele bidraget &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; (øverste underfigur til høyre). 
Det rasjonelle er å ligge så nær &lt;span class="caps"&gt;500TNOK&lt;/span&gt; som mulig, men aldri gå over.
Dersom summen &lt;span class="math"&gt;\(x - T(x) + B(x)\)&lt;/span&gt; synker er det ikke noe insentiv for å tjene mer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eksempel - lineær bidragsfelle.&lt;/strong&gt;
Hva om vi velger en annen funksjon?
Vi kan kompensere de som tjener mindre enn 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt; med en sum slik at alle ender opp med minst 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
Matematisk er funksjonen &lt;span class="math"&gt;\(B(x) = \max(500\text{ TNOK} -x, 0)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/welfare_trap_linear.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;For en som har null i inntekt lønner det seg ikke å jobbe i det hele tatt, med mindre personen klarer å tjene ca. 665 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.
Selv da får han ikke betydelig mer enn hva han fikk med null inntekt.&lt;/p&gt;
&lt;h2 id="bidrag-bx-med-gode-egenskaper"&gt;Bidrag &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; med gode egenskaper&lt;/h2&gt;
&lt;p&gt;Det finnes bidrag &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; som i kombinasjon med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; ikke fører til bidragsfeller.
Funksjoner på formen&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
B(x) = b e^{-x/b} + C \qquad b &amp;lt; \frac{1}{Hk}
\end{align*}&lt;/div&gt;
&lt;p&gt;har gode egenskaper: insentivene er gode og de er billige for staten.&lt;/p&gt;
&lt;p&gt;Summen av skatt og bidrag har gode insentiver dersom det alltid lønner seg å jobbe.
Matematisk er kravet at &lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\underbrace{(x + \epsilon) + B(x + \epsilon) - T(x + \epsilon)}_{\text{netto med $\epsilon$ mer}} &amp;gt; \underbrace{x + B(x) - T(x)}_{\text{netto}}
\end{align*}&lt;/div&gt;
&lt;p&gt;Dersom vi stokker om på likningen og tar grenseverdien &lt;span class="math"&gt;\(\epsilon \to 0\)&lt;/span&gt; får vi kravet
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T'(x) - B'(x) &amp;lt; 1.
\end{align*}&lt;/div&gt;
&lt;p&gt;Vi deriverer skatten &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; og substituerer &lt;span class="math"&gt;\(T'(x) = H\left(1 - e^{-kx} \right)\)&lt;/span&gt;.
Da blir kravet at&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
B'(x) &amp;gt; H\left(1 - e^{-kx} \right) - 1.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Denne likningen sier at bidragene ikke kan synke fort mot null når inntekten &lt;span class="math"&gt;\(x\)&lt;/span&gt; øker.
Dette gir intuitivt mening; bidrag som forsvinner fort fører til bidragsfeller.
Alle funksjoner &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; som oppfyller dette kravet er gyldige, og kvalifisert gjetning leder oss til &lt;span class="math"&gt;\(B(x) = b e^{-x/b} + C\)&lt;/span&gt;, som oppfyller kravet &lt;span class="math"&gt;\(T'(x) - B'(x) &amp;lt; 1\)&lt;/span&gt; når &lt;span class="math"&gt;\(b &amp;lt; 1/(Hk)\)&lt;/span&gt; (se appendiks).&lt;/p&gt;
&lt;p&gt;For eksempel er &lt;span class="math"&gt;\(b=200 \text{ TNOK}\)&lt;/span&gt; og &lt;span class="math"&gt;\(C=0\)&lt;/span&gt; gyldig, og gir følgende omfordeling via skatter og bidrag:&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/valid_b_200k.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Konstantleddet &lt;span class="math"&gt;\(C\)&lt;/span&gt; kalles gjerne borgerlønn (&lt;a href="https://en.wikipedia.org/wiki/Universal_basic_income"&gt;universal basic income&lt;/a&gt;), mens &lt;span class="math"&gt;\(b e^{-x/b}\)&lt;/span&gt; er i kombinasjon med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; en &lt;a href="https://en.wikipedia.org/wiki/Negative_income_tax"&gt;negativ skatt&lt;/a&gt;.
Problemet med &lt;span class="math"&gt;\(B(x) = C\)&lt;/span&gt; er at det er dyrt for staten, fordi alle får bidrag.
I resten av artikkelen setter vi &lt;span class="math"&gt;\(C=0\)&lt;/span&gt; og antar at &lt;span class="math"&gt;\(B(x) = b e^{-x/b}\)&lt;/span&gt;.
Legg merke til at &lt;span class="math"&gt;\(B(0) = b\)&lt;/span&gt;, altså er &lt;span class="math"&gt;\(b\)&lt;/span&gt; summen som en person uten inntekt vil få utbetalt.&lt;/p&gt;
&lt;p&gt;Verdien &lt;span class="math"&gt;\(b\)&lt;/span&gt; må balansere to hensyn: den må være høy nok til at det går an å leve på, samtidig må den være lav nok til å insentivere til arbeid.&lt;/p&gt;
&lt;h2 id="hva-kan-vi-forenkle_1"&gt;Hva kan vi forenkle?&lt;/h2&gt;
&lt;p&gt;Dersom vi innfører et universelt bidrag &lt;span class="math"&gt;\(B(x) = b e^{-x/b}\)&lt;/span&gt; kan vi fjerne eller kutte kraftig i uføretrygd, sykepenger, arbeidsavklaringspenger, dagpenger, tiltakspenger og økonomisk sosialehjelp.
&lt;span class="caps"&gt;NAV&lt;/span&gt; har &lt;a href="https://www.nav.no/_/en/attachment/inline/e89e93e3-3056-47ce-b374-de424818be8a:88d6d362b3654d28c3661c2c23c520251b9d2e47/%C3%85rsrapport%202024%20NAV.pdf"&gt;12 000 årsverk og 15 mrd. i driftsutgiver&lt;/a&gt;, og deler av dette kan kuttes.
Tiden og ressursene som brukes på å sende inn og behandle søknader forsvinner helt.&lt;/p&gt;
&lt;p&gt;Staten betaler også bidrag til friske mennesker for å søtte en rekke aktiviteter.
Eksempelvis var det &lt;a href="https://www.kulturdirektoratet.no/web/guest/sks/vis-artikkel/-/over-12-000-soknadar-til-statens-kunstnarstipend-no-er-vedtaka-klare"&gt;12 000 søkere&lt;/a&gt; på statens kunstnerstipend i 2024, og i 2025 ble det bevilget nesten &lt;a href="https://kommunikasjon.ntb.no/pressemelding/18456124/1-090-kunstnere-far-statens-kunstnerstipend?publisherId=89220&amp;amp;lang=no"&gt;500 millioner&lt;/a&gt; kroner.
Mange slike ordninger kan fjernes.
Mennesker som ønsker å lage kunst eller gjøre noe helt annet (som kan være vel så samfunnsnyttig men ikke anerkjent av staten), står fritt til å gjøre det—dersom de er villige til å leve på bidraget &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt;.
Dersom kunsten selger, vil de få mer inntekt, miste bidrag og betale skatt—men de vil alltid ende opp med mer penger.&lt;/p&gt;
&lt;h1 id="en-realistisk-beregning"&gt;En realistisk beregning&lt;/h1&gt;
&lt;p&gt;I denne seksjonen forsøker vi grovt å regne ut hva &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; vil koste.
Her er noen store utgiftsposter, hentet fra &lt;a href="https://www.nav.no/_/en/attachment/inline/e89e93e3-3056-47ce-b374-de424818be8a:15067f1398be3d3be4129e606c7d69641c9e3274/%C3%85rsrapport%202024%20NAV.pdf"&gt;NAVs årsrapport 2024&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uføretrygd: 125 269 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Sykepengetilfeller: 68 068 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Arbeidsavklaringspenger: 46 926 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Dagpenger: 13 300 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Hjelpemidler: 10 321 &lt;span class="caps"&gt;MNOK&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Til sammen er dette 264 milliarder kroner.
I følge &lt;a href="https://www.ssb.no/arbeid-og-lonn/sysselsetting/statistikk/tilknytning-til-arbeid-utdanning-og-velferdsordninger/artikler/18-prosent-i-yrkesaktiv-alder-er-utenfor"&gt;&lt;span class="caps"&gt;SSB&lt;/span&gt;&lt;/a&gt; er 622 000 personer i yrkesaktiv alder utenfor arbeidslivet (omtrent 18 %).
Fordeler vi 264 milliarder på 622 000 personer kan hver person få 424 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Ettersom &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; også deler ut penger til de som ikke har null i inntekt, må vi ta hensyn til de 82 % som er i arbeidslivet.
Jeg fant ingen gode data som viser inntektsfordeling for alle i Norge, men om vi baserer oss på &lt;a href="https://www.ssb.no/arbeid-og-lonn/lonn-og-arbeidskraftkostnader/artikler/hvor-stort-er-egentlig-lonnsgapet-mellom-kvinner-og-menn"&gt;fulltidsekvivalenter&lt;/a&gt; for de 82 % som er i arbeidslivet og kombinerer det med de 18 % som er utenfor, kommer vi frem til at 264 milliarder kan finansiere &lt;span class="math"&gt;\(b = 271 \text{ TNOK}\)&lt;/span&gt;.
At vi bruker fulltidsekvivalenter gjør at &lt;span class="math"&gt;\(b\)&lt;/span&gt; overestimeres.
At vi verken inkluderer flere støtteordninger eller administrative kostnader gjør at &lt;span class="math"&gt;\(b\)&lt;/span&gt; underestimeres.
Disse effektene kansellerer hverandre til en viss grad, og i sum er nok &lt;span class="math"&gt;\(b\)&lt;/span&gt; estimert noelunde riktig.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/maximal_benefits.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Men hvor mye bør staten dele ut?
Det finnes ingen klar fasit, men her er to ankerpunkter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.oslomet.no/om/sifo/referansebudsjettet"&gt;SIFOs referansebudsjett&lt;/a&gt; er på omtrent 190 &lt;span class="caps"&gt;TNOK&lt;/span&gt; i året (eksl. bolig, ferier og helsetjenester).
  Boliginformasjon kan vi finne &lt;a href="https://www.ssb.no/bygg-bolig-og-eiendom/bolig-og-boforhold/statistikk/boforhold-levekarsundersokelsen/artikler/kraftig-okning-i-bokostnader-for-boligeiere"&gt;hos &lt;span class="caps"&gt;SSB&lt;/span&gt;&lt;/a&gt;, som estimerer typisk boligkostnad for eiere til 130 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år og leietakere til 111 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år.
  En person trenger derfor omtrent 190 + 120 = 310 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år for å leve greit.&lt;/li&gt;
&lt;li&gt;Lavinntekstsgrensa måler relativ fattigdom og er definert til å være 60% av medianinntekt etter skatt.
  Grensa er på &lt;a href="https://www.ssb.no/inntekt-og-forbruk/inntekt-og-formue/artikler/hvor-mange-er-fattige-i-norge"&gt;285 &lt;span class="caps"&gt;TNOK&lt;/span&gt;&lt;/a&gt; i 2025.
  
Oppsummert har staten råd til &lt;span class="math"&gt;\(b \approx 271 \text{ TNOK}\)&lt;/span&gt;, og dette nivået samsvarer godt med SIFOs referansebudsjett og fattigdomsgrensen.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="oppsummering-bidrag-og-sttteordninger"&gt;Oppsummering: Bidrag og støtteordninger&lt;/h2&gt;
&lt;p&gt;Bidrag og støtteordninger er i hovedsak til for syke og arbeidsledige.
Dagens systemer kan være vanskelige å navigere, kreve mye dokumentasjon, og kan disinsentivere arbeid.
Staten må bedømme hva som er verdifullt for samfunnet, selv om det kanskje ikke er kommersielt levedyktig.
Stipendordninger kan føre til &lt;a href="https://www.nettavisen.no/okonomi/er-i-kulturradet-fikk-16-5-millioner-i-statsstotte/s/5-95-2611248"&gt;habilitetsutfordringer&lt;/a&gt; og staten må bruke ressurser på å bestemme hvem som får hva.&lt;/p&gt;
&lt;p&gt;Vi innfører en bidragsfunksjon &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt;, som sammen med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; gir negativ skatt til de som tjener lite.
Dette krever ingen søknadsprosess eller vurdering fra statens side.
Vi fjerner ikke tiltak som jobbsøkerkurs og økonomisk veiledning, ei heller bidrag til rullestoler, proteser, osv.
Bidragene kunne vært satt til omtrent 300 &lt;span class="caps"&gt;TNOK&lt;/span&gt; per år, men det eksakte beløpet er en subjektiv vurdering.
I hvilken grad barn, studenter og pensjonister omfattes av ordningen kan diskuteres.
Det enkleste er å gi bidrag til disse gruppene og fjerne eksisterende støtteordninger.&lt;/p&gt;
&lt;h1 id="formuesskatt"&gt;Formuesskatt&lt;/h1&gt;
&lt;p&gt;Denne delen av artikkelen er uten sammenheng med resten, som i hovedsak handler om inntekt og bidrag, men la oss se på formueskatt også.&lt;/p&gt;
&lt;p&gt;Den marginale &lt;a href="https://www.skatteetaten.no/satser/formuesskatt/"&gt;formueskatten&lt;/a&gt; har to trinn: ett på 1.76 &lt;span class="caps"&gt;MNOK&lt;/span&gt; (1 %) og ett på 20.7 &lt;span class="caps"&gt;MNOK&lt;/span&gt; (1.1 %).
Den er med andre ord langt mindre progressiv enn inntektsskatt, og den slår inn relativt tidlig.
Effekten er nedjustert av verdsettingsrabatter; eksempelvis har primærbolig en rabatt på hele 75 % mens aksjer har en rabatt på 20 %.&lt;/p&gt;
&lt;p&gt;De rike blir rikere på grunn av renters-rente effekten.
Den brede aksjeindeksen S&amp;amp;P 500 har hatt en gjennomsnittlig inflasjonsjustert avkastning på &lt;a href="https://www.inflationtool.com/adjusted-prices/spy"&gt;7.65 % de siste 30 årene&lt;/a&gt;.
En rik person med 100 &lt;span class="caps"&gt;MNOK&lt;/span&gt; investert vil derfor få 7.65 &lt;span class="caps"&gt;MNOK&lt;/span&gt; i avkastning.
Anta at denne avkastningen realiseres og beskattes med 3.46 &lt;span class="caps"&gt;MNOK&lt;/span&gt; (med &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;) slik at personen står igjen med en gevinst på 4.2 &lt;span class="caps"&gt;MNOK&lt;/span&gt;.
Formuen er nå 100 + 4.2 = 104.2 &lt;span class="caps"&gt;MNOK&lt;/span&gt;, og formueskatten blir 1.1 &lt;span class="caps"&gt;MNOK&lt;/span&gt;, så personen står igjen med 103.1 &lt;span class="caps"&gt;MNOK&lt;/span&gt;.
Ved å sette pengene i en bred indeks som S&amp;amp;P 500, som ikke krever noe arbeid eller oppfølging, kan personen hente ut 3.1 &lt;span class="caps"&gt;MNOK&lt;/span&gt; per år.
Formuen vil ha samme avkastning hvert eneste år.&lt;/p&gt;
&lt;p&gt;Figuren nedenfor viser den marginale formueskatten og simuleringer av ulike formuer i S&amp;amp;P 500.
Formuene vokser lineært på log skala, altså eksponentielt.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/wealth_tax.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Dagens marginale formueskatt er i praksis flat, og da vil formuene enten vokse evig (om formueskatten er lav) eller gå mot null (om formuesskatten er høy).
Ved å konstruere en marginal formuesskatt som er mer progressiv kan vi begrense de rikes formuer.&lt;/p&gt;
&lt;p&gt;La &lt;span class="math"&gt;\(y\)&lt;/span&gt; være formue.
Ved å bruke en sigmoid &lt;span class="math"&gt;\(z = 1 / (1 + e^{-z})\)&lt;/span&gt; på log-skala kan vi konstruere en formuesskatt:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
W'(y) = \frac{H}{1 + (y / y_0)^{-k}}
\qquad
W(y) = \frac{H y^{1 + k}}{(k+1) y_0^k} {_2F_1}\left(1, 1 + 1/k, 2 + 1/k, -(y/y_0)^k\right)
\end{align*}&lt;/div&gt;
&lt;p&gt;
Parameteren &lt;span class="math"&gt;\(H\)&lt;/span&gt; er maksimal marginal formuesskatt, &lt;span class="math"&gt;\(y_0\)&lt;/span&gt; er den formuen som gir &lt;span class="math"&gt;\(W'(y_0) = H/2\)&lt;/span&gt; og &lt;span class="math"&gt;\(k &amp;gt; 0\)&lt;/span&gt; bestemmer hvor raskt sigmoid-funksjonen vokser.
Den marginale formueskatten &lt;span class="math"&gt;\(W'(y)\)&lt;/span&gt; er gitt av en relativt uskyldig formel, mens &lt;span class="math"&gt;\(W(y)\)&lt;/span&gt; er uttrykt ved hjelp av en &lt;a href="https://en.wikipedia.org/wiki/Hypergeometric_function"&gt;hypergeometrisk funksjon&lt;/a&gt; &lt;span class="math"&gt;\({_2F_1}\)&lt;/span&gt; (se appendiks).
Man trenger ikke å vite noe om hypergeometriske funksjoner for å bruke eller forstå denne funksjonen.&lt;/p&gt;
&lt;p&gt;Den progressive marginale formuesskatten &lt;span class="math"&gt;\(W'(y)\)&lt;/span&gt; er vist i figuren nedenfor, og sammenlignet med veksten til ulike formuer.
Parametrene er satt ut fra øyemål for å vise effekten på en tydelig måte.
Merk at veksten til ekstremt store formuer begrenses, mens mindre formuer får vokse.
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/wealth_tax_progressive.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Med &lt;span class="math"&gt;\(W(y)\)&lt;/span&gt; betaler alle formuesskatt, men de fleste betaler veldig lite.
En formue på 100 &lt;span class="caps"&gt;TNOK&lt;/span&gt; gir 19 kr i formueskatt, 1 &lt;span class="caps"&gt;MNOK&lt;/span&gt; gir 1200 kr, 10 &lt;span class="caps"&gt;MNOK&lt;/span&gt; gir 64 &lt;span class="caps"&gt;TNOK&lt;/span&gt; og 100 &lt;span class="caps"&gt;MNOK&lt;/span&gt; gir 2.2 &lt;span class="caps"&gt;MNOK&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Alle eksemplene i denne seksjonen antar at formuen er likvid.
Dette er ikke tilfellet dersom formuen består av aksjer i selskaper som ikke er på børs, eller om formuen er i eiendom.
Formuesskatt kan føre til at man mister kontrollen over sitt eget selskap eller mister sin egen bolig.
En bolig gir bruksverdi til eieren på en annen måte enn et selskap, så argumentet for formuesskatt på eiendom er sterkere enn argumentet for formuesskatt på illikvide aksjer.
Dersom vi verken har formueskatt eller arveskatt, kan en rik familie sette formuen i aksjemarkedet og oppnå eksponentiell vekst for all fremtid.&lt;/p&gt;
&lt;h1 id="oppsummering"&gt;Oppsummering&lt;/h1&gt;
&lt;p&gt;I dette tankeeksperimentet endret vi skattesystemet ved å innføre to funksjoner: &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; for skatt og &lt;span class="math"&gt;\(B(x)\)&lt;/span&gt; for bidrag.
Parametrene (&lt;span class="math"&gt;\(H\)&lt;/span&gt;, &lt;span class="math"&gt;\(k\)&lt;/span&gt; og &lt;span class="math"&gt;\(b\)&lt;/span&gt;) eliminerer kompleksiteten i skatteberegninger, mange av NAVs støtteordninger og en rekke andre overføringsmekanismer.
Den generelle idéen om borgerlønn og negativ inntektsskatt er gammel, mens de spesifikke funksjonene og deres egenskaper er mitt eget bidrag.
Les gjerne &lt;a href="https://civita.no/content/uploads/2018/01/Civitanotat_38_2017.pdf"&gt;Civita-notat nr. 38 om borgerlønn&lt;/a&gt; for en grundig gjennomgang av borgerlønn i norsk sammenheng.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/simple_taxes/redistribution_T_B.png" style="display: block; 
margin-left: auto; 
margin-right: auto; 
width: 95%; 
max-width: 750px;
image-rendering: crisp-edges;"/&gt;&lt;/p&gt;
&lt;p&gt;Økonomiske insentiver som &lt;a href="https://www.skatteetaten.no/satser/finnmarksfradraget/"&gt;Finnmarksfradraget&lt;/a&gt; i skatteberegningen og &lt;a href="https://lanekassen.no/nb-NO/gjeld-og-betaling/finnmark-eller-nord-troms/#samtykke-banner"&gt;sletting av gjeld&lt;/a&gt; for beboere i Finnmark kan modelleres ved å velge litt andre parametre (f.eks. &lt;span class="math"&gt;\(b\)&lt;/span&gt; eller &lt;span class="math"&gt;\(C\)&lt;/span&gt;) i Finnmark.
Her bør staten vise måtehold, ettersom mye av poenget forsvinner dersom man får en eksplosjon i antall parametre.
Vi viste også at en sigmoid-funksjon &lt;span class="math"&gt;\(W(y)\)&lt;/span&gt; som marginal formueskatt begrenser ekstrem formuesvekst.&lt;/p&gt;
&lt;p&gt;Noen av fordelene med et slikt system er:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Å øke maksimal marginalskatt &lt;span class="math"&gt;\(H\)&lt;/span&gt; kan gi skattelette til omtrent 80 % av befolkningen.&lt;/li&gt;
&lt;li&gt;Skatteoptimaliseringsmuligheter som primært kommer de rike til gode fjernes.&lt;/li&gt;
&lt;li&gt;Bidragsfeller som i dag fanger mange &lt;span class="caps"&gt;NAV&lt;/span&gt;-brukere blir eliminert.&lt;/li&gt;
&lt;li&gt;Den administrative besparelsen ville vært stor. Tusenvis av årsverk kuttes og borgere slipper søknadsprosesser.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Det er imidlertid et tankeeksperiment og en øvelse i matematikk, ikke et gjennomtenkt politisk forslag.
Mange av kompleksitetene vi ønsker å fjerne reflekterer legitime forsøk på å balansere konkurrerende samfunnsmål.
Noen vil mene at det er moralsk riktig å gi støtte ut fra hvem som fortjener det eller ikke, selv om det koster å gjennomføre slike vurderinger.
En mer aggressiv formueskatt kan utløse kapitalflukt.
Mange ville brukt økonomisk trygghet til å investere i utdanning, starte bedrifter eller ta seg av familie.
Andre ville potensielt redusert arbeidsinnsatsen eller i verste fall ty til destruktiv atferd.
En implementering ville krevd utredninger av atferdsresponser, overgangsordninger og konsekvenser.
Men hvis det er et land som har råd til å utrede, så er det Norge.&lt;/p&gt;
&lt;h2 id="referanser-og-videre-lesing"&gt;Referanser og videre lesing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nav.no/no/nav-og-samfunn/statistikk/flere-statistikkomrader/utbetalinger-til-personer-i-norge-per-fylke-og-kommune"&gt;Utbetalinger til personer i Norge - Statistikk for 2024&lt;/a&gt; - &lt;span class="caps"&gt;NAV&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nav.no/no/nav-og-samfunn/statistikk/flere-statistikkomrader/utbetalinger-til-personer-i-norge-per-fylke-og-kommune"&gt;Årsrapport 2024&lt;/a&gt; - &lt;span class="caps"&gt;NAV&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.regjeringen.no/no/dokumenter/meld.-st.-1-20242025/id3056833/"&gt;Nasjonalbudsjettet 2025&lt;/a&gt; - Regjeringen&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nrk.no/ytring/vi-burde-snakke-om-borgerlonn-1.11814756"&gt;Hvorfor snakker vi ikke om borgerlønn?&lt;/a&gt; - &lt;span class="caps"&gt;NRK&lt;/span&gt; Ytring (2014)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://civita.no/content/uploads/2018/01/Civitanotat_38_2017.pdf"&gt;Borgerlønn&lt;/a&gt; - Civita-notat nr. 38 (2017)
 &lt;a href="https://www.smartepenger.no/103-skatt/1890-tips-som-kan-kutte-skatten-din"&gt;50 tips som kan kutte skatten din&lt;/a&gt; - smartepenger.no&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Negative_income_tax"&gt;Negative income tax&lt;/a&gt; - Wikipedia&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mdpi.com/2071-1050/12/22/9459"&gt;Is There Empirical Evidence on How the Implementation of a Universal Basic Income (&lt;span class="caps"&gt;UBI&lt;/span&gt;) Affects Labour Supply? A Systematic Review&lt;/a&gt; - Artikkel&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="appendiks-a-bevis-for-at-bidragsfunksjonen-har-gode-insentiver"&gt;Appendiks A: Bevis for at bidragsfunksjonen har gode insentiver&lt;/h2&gt;
&lt;p&gt;I dette appendikset skal vi vise at når vi velger funksjoner
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = \frac{H(e^{-kx} + kx - 1)}{k} \qquad \text{og}\qquad B(x) = Abe^{-x/b} + C
\end{align*}&lt;/div&gt;
&lt;p&gt;
med &lt;span class="math"&gt;\(0 \leq H &amp;lt; 1\)&lt;/span&gt;, &lt;span class="math"&gt;\(b &amp;gt; 0\)&lt;/span&gt;, &lt;span class="math"&gt;\(k &amp;gt; 0\)&lt;/span&gt; og &lt;span class="math"&gt;\(A &amp;gt; 0\)&lt;/span&gt;, så er har vi alltid gode insentiver fordi
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T'(x) - B'(x) &amp;lt; 1
\end{align*}&lt;/div&gt;
&lt;p&gt;
for alle &lt;span class="math"&gt;\(x &amp;gt; 0\)&lt;/span&gt; dersom &lt;span class="math"&gt;\(Hkb &amp;lt; A\)&lt;/span&gt; og &lt;span class="math"&gt;\(0 &amp;lt; A \leq 1\)&lt;/span&gt;.
Det naturlige er å velge &lt;span class="math"&gt;\(A=1\)&lt;/span&gt;, fordi da synker bidraget raskest og det blir billigst for staten.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bevis.&lt;/strong&gt;
Vi deriverer funksjonene og setter dem inn i ulikheten.
Da får vi
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f(x) &amp;amp;= -T'(x) + B(x) + 1 &amp;gt; 0 \\  
f(x) &amp;amp;= H\left(e^{-kx} - 1 \right) - A e^{-x/b} + 1 &amp;gt; 0.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Vi ser også at
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
f'(x) &amp;amp;= -Hk e^{-kx} + \frac{A}{b} e^{-x/b} \\
f'(x) &amp;amp;= 0 \, \Rightarrow \, x (k - \frac{1}{b}) = \ln\left( \frac{Hkb}{A} \right).
\end{align*}&lt;/div&gt;
&lt;p&gt;
Dersom &lt;span class="math"&gt;\(f(x)\)&lt;/span&gt; er positiv og økende når &lt;span class="math"&gt;\(x=0\)&lt;/span&gt;, konvergerer mot et positivt tall og har maksimalt ett ekstremalpunkt når &lt;span class="math"&gt;\(x &amp;gt; 0\)&lt;/span&gt;, så er &lt;span class="math"&gt;\(f(x) &amp;gt; 0\)&lt;/span&gt; for alle &lt;span class="math"&gt;\(x &amp;gt; 0\)&lt;/span&gt; og ulikheten er gyldig.&lt;/p&gt;
&lt;p&gt;Vi observerer at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span class="math"&gt;\(f(0) = -A + 1 \geq 0 \Rightarrow A \leq 1\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(f'(0) = -Hk + A/b &amp;gt; 0 \Rightarrow A &amp;gt; Hkb\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\lim_{x \to \infty} f(x) = 1 - H &amp;gt; 0\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(f(x)\)&lt;/span&gt; har ett ekstremalpunkt fordi &lt;span class="math"&gt;\(f'(x) = 0\)&lt;/span&gt; har én løsning&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dette gir oss kravene (1) &lt;span class="math"&gt;\(A \leq 1\)&lt;/span&gt; og (2) &lt;span class="math"&gt;\(A &amp;gt; Hkb\)&lt;/span&gt;.
I artikkelen valgte vi &lt;span class="math"&gt;\(A=1\)&lt;/span&gt;, og da blir kravet for &lt;span class="math"&gt;\(b\)&lt;/span&gt; at &lt;span class="math"&gt;\(b &amp;lt; 1/(Hk)\)&lt;/span&gt;. &lt;/p&gt;
&lt;h2 id="appendiks-b-formuesskatten"&gt;Appendiks B: Formuesskatten&lt;/h2&gt;
&lt;p&gt;I dette appendikset skal vi utlede formuesskatten &lt;span class="math"&gt;\(W(y)\)&lt;/span&gt; fra den marginale formuesskatten &lt;span class="math"&gt;\(W'(y)\)&lt;/span&gt;.
Vi definerte den marginale formuesskatten som &lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
W'(y) = \frac{H}{1 + (y / y_0)^{-k}} = \frac{H}{1 + \exp(-k \ln (y / y_0))}.
\end{align*}&lt;/div&gt;
&lt;p&gt;Målet er å evaluere integralet&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\int_0^y W'(z) \, dz = H \int_0^y \frac{1}{1 + \exp(-k \ln (z / y_0))} \, dz
\end{align*}&lt;/div&gt;
&lt;p&gt;for å finne den totale formuesskatten ved et beløp &lt;span class="math"&gt;\(Y\)&lt;/span&gt;.
Først gjør vi substitusjonen &lt;span class="math"&gt;\(u = \ln(z/y_0)\)&lt;/span&gt;, som transformerer integralet til
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
H y_0 \int_{-\infty}^{\ln(y/y_0)} \frac{e^u}{1 + e^{-ku}} \, du = H y_0 \int_{-\infty}^{\ln(y/y_0)} \frac{e^u e^{ku}}{1 + e^{ku}} \, du.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Deretter gjør vi substitusjonen &lt;span class="math"&gt;\(z = e^{ku}\)&lt;/span&gt;, som transformerer integralet til
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\frac{H y_0}{k} \int_{0}^{(y/y_0)^k} \frac{z^{1/k}}{1 + z} \, dz
\end{align*}&lt;/div&gt;
&lt;p&gt;
og dette er et kjent integral som kan formuleres som en &lt;a href="https://en.wikipedia.org/wiki/Hypergeometric_function"&gt;hypergeometrisk funksjon&lt;/a&gt;.
Vi kan bruke identiteten
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\int \frac{x^{1/k}}{1 + x} \, dx = \frac{k x^{1 + 1/k}}{k+1} \, {_2F_1}\left( 1, 1 + 1/k, 2 + 1/k, -x \right)
\end{align*}&lt;/div&gt;
&lt;p&gt;
som gir oss vårt endelig svar:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
W(y) = \int_0^y W'(z) \, dz = \frac{H y^{1 + k}}{(k+1) y_0^k} \, {_2F_1}\left(1, 1 + 1/k, 2 + 1/k, -(y/y_0)^k\right)
\end{align*}&lt;/div&gt;
&lt;h2 id="appendiks-c-kode"&gt;Appendiks C: Kode&lt;/h2&gt;
&lt;p&gt;Her er Python-kode som implementerer hovedidéene i artikkelen.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dataclasses&lt;/span&gt;  &lt;span class="c1"&gt;# Python 3.12&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;abc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstractmethod&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# 2.3.3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy&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="nn"&gt;sp&lt;/span&gt;  &lt;span class="c1"&gt;# 1.16.2&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Abstract base class for transfer functions (taxes, benefits, etc.)."""&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;marginal&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;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Marginal rate at income x."""&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;absolute&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;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Absolute value at income x."""&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;average&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;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Average rate: absolute(x) / x."""&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;absolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&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="nd"&gt;@dataclasses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Tax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Class representing a tax function T(x).&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; T = Tax(k=2.787914e-06, H=0.474)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; T.absolute(500_000)  # Current norwegian tax system: 113643.99&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(109160.85121307125)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; T.absolute(1_000_000)  # Current norwegian tax system: 313248.14&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(314445.0244402999)&lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;

    &lt;span class="c1"&gt;# Defaults that give best-fit to current tax system in 2025,&lt;/span&gt;
    &lt;span class="c1"&gt;# weighted by income of population.&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.8745e-06&lt;/span&gt;
    &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.4772&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;__post_init__&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="k"&gt;assert&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;k&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="k"&gt;assert&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;H&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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;marginal&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;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Marginal tax rate T'(x)."""&lt;/span&gt;
        &lt;span class="n"&gt;H&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&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;k&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&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="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;absolute&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;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Absolute value of tax T(x)."""&lt;/span&gt;
        &lt;span class="n"&gt;H&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H&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;k&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;k&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;np&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="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k&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;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@dataclasses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Benefit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Class representing a benefit function B(x).&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; B = Benefit(300_000)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; B.absolute(0)&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(300000.0)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; B.absolute(300_000)  # exp(-1) * 300_000 = 110363&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(110363.8323514327)&lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;

    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;271_000&lt;/span&gt;  &lt;span class="c1"&gt;# Somewhat realistic value&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;__post_init__&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="k"&gt;assert&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;b&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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;marginal&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;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Marginal benefit rate B'(x)."""&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;np&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="n"&gt;x&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;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;absolute&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;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Absolute value of benefits B(x)."""&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;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&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="n"&gt;x&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;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@dataclasses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;WealthTax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Class representing a wealth tax function W(y).&lt;/span&gt;

&lt;span class="sd"&gt;    Uses a logistic-based wealth tax with hypergeometric function&lt;/span&gt;
&lt;span class="sd"&gt;    for absolute tax calculation.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; W = WealthTax(y_0=10_000_000, H=0.03, k=1.0)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; W.marginal(10_000_000)  # At threshold, marginal rate is H/2&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(0.015)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; W.average(2_000_000)  # Average tax on 2 MNOK wealth&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(0.0026517664809068064)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; W.average(20_000_000)  # Average tax on 20 MNOK wealth&lt;/span&gt;
&lt;span class="sd"&gt;    np.float64(0.013520815669978353)&lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;

    &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000_000&lt;/span&gt;  &lt;span class="c1"&gt;# Wealth threshold&lt;/span&gt;
    &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.03&lt;/span&gt;  &lt;span class="c1"&gt;# Maximum marginal rate&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Steepness parameter&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;__post_init__&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="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&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;H&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="k"&gt;assert&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;y_0&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="k"&gt;assert&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;k&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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;marginal&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;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Marginal wealth tax rate W'(y)."""&lt;/span&gt;
        &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_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;H&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;k&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;/&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;np&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="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;absolute&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;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Absolute value of wealth tax W(y)."""&lt;/span&gt;
        &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y_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;H&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;k&lt;/span&gt;
        &lt;span class="n"&gt;power&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;y_0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;
        &lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;power&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;special&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hyp2f1&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="mi"&gt;1&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;k&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="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pytest&lt;/span&gt;

    &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;"--doctest-modules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;numerical_derivative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;h&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;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Test that derivatives are correct for all classes with defaults&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Benefit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WealthTax&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;analytical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marginal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;numerical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numerical_derivative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;analytical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rtol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 13 Feb 2026 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2026-02-13:/articles/2026/skatter-og-bidrag</guid><category>articles</category><category>mathematics</category></item><item><title>Practical Linear Algebra with NumPy</title><link>https://tommyodland.com/articles/2026/practical-linear-algebra-with-numpy</link><description>&lt;p&gt;Whether you’re solving differential equations, training neural networks, inferring parameters in a statistical model or solving optimization problems, you’ll find linear algebra at the core of almost every routine.
Still, mistakes in the application of linear algebra are all too common.
Even though it’s a novice numerical linear algebra error, I’ve seen professors solve &lt;span class="math"&gt;\(A \boldsymbol{x} = b\)&lt;/span&gt; as &lt;code&gt;b = np.linalg.inv(A) @ x&lt;/code&gt; and call it a day.&lt;/p&gt;
&lt;p&gt;Here’s a list of beginner linear algebra mistakes, followed by some tips for writing better and faster numerical code.
Remember to start with a correct implementation before working on speed.&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#dont-compare-floats"&gt;Don’t compare floats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-multiply-diagonal-matrices"&gt;Don’t multiply diagonal matrices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-add-diagonal-matrices"&gt;Don’t add diagonal matrices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-multiply-matrices-in-arbitrary-order"&gt;Don’t multiply matrices in arbitrary order&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-compute-a-product-if-you-only-need-the-diagonal"&gt;Don’t compute a product if you only need the diagonal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-form-the-inverse-matrix"&gt;Don’t form the inverse matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-use-memory-you-dont-need"&gt;Don’t use memory you don’t need&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dont-use-pure-python-loops"&gt;Don’t use pure Python loops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-use-the-woodbury-identity"&gt;Do use the Woodbury identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-think-about-what-the-meaning-is"&gt;Do think about what the meaning is&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-time-everything"&gt;Do time everything&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-read-the-documentation"&gt;Do read the documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#do-consider-approximations"&gt;Do consider approximations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#resources-and-further-reading"&gt;Resources and further reading&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h3 id="dont-compare-floats"&gt;Don’t compare floats&lt;/h3&gt;
&lt;p&gt;Floats are an approximation of real numbers, and comparing them bit-by-bit is too stringent:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&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="mi"&gt;13&lt;/span&gt;&lt;span class="p"&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="mf"&gt;13.0&lt;/span&gt;  &lt;span class="c1"&gt;# False&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&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="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;,&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;qr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="o"&gt;@&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;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# False&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Use &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.isclose.html"&gt;&lt;code&gt;numpy.isclose&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.allclose.html"&gt;&lt;code&gt;numpy.allclose&lt;/code&gt;&lt;/a&gt; in general, and consider &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.testing.assert_allclose.html"&gt;&lt;code&gt;numpy.testing.assert_allclose&lt;/code&gt;&lt;/a&gt; for testing.&lt;/p&gt;
&lt;h3 id="dont-multiply-diagonal-matrices"&gt;Don’t multiply diagonal matrices&lt;/h3&gt;
&lt;p&gt;The multiplication &lt;span class="math"&gt;\(A \operatorname{diag}(\boldsymbol{v})\)&lt;/span&gt; is just a column-wise scaling of &lt;span class="math"&gt;\(A\)&lt;/span&gt; by &lt;span class="math"&gt;\(\boldsymbol{v}\)&lt;/span&gt;. Similarly, &lt;span class="math"&gt;\(\operatorname{diag}(\boldsymbol{v}) A\)&lt;/span&gt; is a row-wise scaling.
Implement the scaling directly instead of multiplying with the diagonal matrix, since using a diagonal matrix mostly does unnecessary work by multiplying the entries of &lt;span class="math"&gt;\(A\)&lt;/span&gt; with zeros.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy&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="nn"&gt;sp&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 6.3 ms ± 1.01 ms per loop&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;v&lt;/span&gt;  &lt;span class="c1"&gt;# 369 µs ± 35.9 µs per loop&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="dont-add-diagonal-matrices"&gt;Don’t add diagonal matrices&lt;/h3&gt;
&lt;p&gt;Don’t explicitly form &lt;span class="math"&gt;\(I\)&lt;/span&gt; when computing &lt;span class="math"&gt;\(A + I\)&lt;/span&gt;. If &lt;span class="math"&gt;\(A\)&lt;/span&gt; has size &lt;span class="math"&gt;\(n \times n\)&lt;/span&gt;, then you’re adding &lt;span class="math"&gt;\(n\)&lt;/span&gt; ones and &lt;span class="math"&gt;\(n^2 - n\)&lt;/span&gt; unnecessary zeroes.
Instead use &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.fill_diagonal.html"&gt;&lt;code&gt;numpy.fill_diagonal&lt;/code&gt;&lt;/a&gt; and do it like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eye&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&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;I&lt;/span&gt;  &lt;span class="c1"&gt;# 1.07 ms ± 68.2 µs per loop&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&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;diagonal&lt;/span&gt;&lt;span class="p"&gt;()&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="c1"&gt;# 8.66 μs ± 141 ns per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This trick can be used to speed up adding arbitrary diagonal matrixes too.&lt;/p&gt;
&lt;h3 id="dont-multiply-matrices-in-arbitrary-order"&gt;Don’t multiply matrices in arbitrary order&lt;/h3&gt;
&lt;p&gt;Matrix multiplication is associative, so the expressions &lt;span class="math"&gt;\((AB)\boldsymbol{x}\)&lt;/span&gt; and &lt;span class="math"&gt;\(A(B\boldsymbol{x})\)&lt;/span&gt; evaluate to the same value—but the computational cost is not the same.
Your can either work out the &lt;a href="https://en.wikipedia.org/wiki/Matrix_chain_multiplication"&gt;optimal ordering&lt;/a&gt; by hand, or let &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.linalg.multi_dot.html"&gt;&lt;code&gt;numpy.linalg.multi_dot&lt;/code&gt;&lt;/a&gt; do the job for you.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&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;B&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="c1"&gt;# 18.8 ms ± 1.11 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="p"&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;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 378 µs ± 114 µs per loop&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;B&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;  &lt;span class="c1"&gt;# 19 ms ± 766 µs per loop&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multi_dot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;A&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="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# 378 µs ± 110 µs per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Consider another example: computing &lt;span class="math"&gt;\(A \boldsymbol{v} \boldsymbol{v}^T A\)&lt;/span&gt;.
Here &lt;a href="https://numpy.org/doc/stable/reference/generated/numpy.linalg.multi_dot.html"&gt;&lt;code&gt;numpy.linalg.multi_dot&lt;/code&gt;&lt;/a&gt; won’t be of much help, since &lt;span class="math"&gt;\(\boldsymbol{v}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{v}^T\)&lt;/span&gt; are the same 1 dimensional array in NumPy.
Should we implement the computation as &lt;span class="math"&gt;\(A (\boldsymbol{v} \boldsymbol{v}^T) A\)&lt;/span&gt; or &lt;span class="math"&gt;\((A \boldsymbol{v}) (\boldsymbol{v}^T A)\)&lt;/span&gt;?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;  &lt;span class="c1"&gt;# 910 ms ± 61.6 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&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;v&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;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 41.5 ms ± 3.95 ms per loop&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&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;v&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;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;In the first approach we perform one outer product and two matrix multiplications.&lt;/li&gt;
&lt;li&gt;In the second approach we perform one outer product and two matrix-vector multiplications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If &lt;span class="math"&gt;\(A \in \mathbb{R}^{m \times n}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{v} \in \mathbb{R}^{n}\)&lt;/span&gt;, then computing the matrix-vector multiplication &lt;span class="math"&gt;\(A \boldsymbol{v}\)&lt;/span&gt; uses &lt;span class="math"&gt;\(\mathcal{O}(mn)\)&lt;/span&gt; operations. If &lt;span class="math"&gt;\(A \in \mathbb{R}^{m \times n}\)&lt;/span&gt; and &lt;span class="math"&gt;\(B \in \mathbb{R}^{n \times k}\)&lt;/span&gt;, then computing the matrix-matrix multiplication &lt;span class="math"&gt;\(A B\)&lt;/span&gt; uses &lt;span class="math"&gt;\(\mathcal{O}(mnk)\)&lt;/span&gt; operations.
So a matrix-vector multiplication is &lt;span class="math"&gt;\(\mathcal{O}(k)\)&lt;/span&gt; faster than a matrix-matrix multiplication.&lt;/p&gt;
&lt;h3 id="dont-compute-a-product-if-you-only-need-the-diagonal"&gt;Don’t compute a product if you only need the diagonal&lt;/h3&gt;
&lt;p&gt;Consider computing &lt;span class="math"&gt;\(\operatorname{diag}(A B)\)&lt;/span&gt;.
Forming the full product &lt;span class="math"&gt;\(A B\)&lt;/span&gt; only to extract the diagonal is wasteful.
Instead, compute the diagonal of the product as &lt;code&gt;(B.T * A).sum(axis=1)&lt;/code&gt; in NumPy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&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;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 3.55 s ± 151 ms per loop&lt;/span&gt;
&lt;span class="p"&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;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&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="c1"&gt;# 188 ms ± 5.44 ms per loop&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&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;B&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&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;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="dont-form-the-inverse-matrix"&gt;Don’t form the inverse matrix&lt;/h3&gt;
&lt;p&gt;There are several reasons why you &lt;a href="https://www.johndcook.com/blog/2010/01/19/dont-invert-that-matrix/"&gt;don’t invert that matrix&lt;/a&gt;: it’s slow and it’s not numerically stable.
Instead of explicitly solving &lt;span class="math"&gt;\(A\boldsymbol{x} = b\)&lt;/span&gt; by forming and multiplying by &lt;span class="math"&gt;\(A^{-1}\)&lt;/span&gt;, use a solver.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&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;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&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="c1"&gt;# 355 ms ± 52.5 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&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="c1"&gt;# 86.7 ms ± 8.33 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now let’s look at a slightly more advanced computation, which combines the idea of not forming inverses with the idea of not computing products in arbitrary order.
We want to compute the product &lt;span class="math"&gt;\(A B^{-1} C\)&lt;/span&gt;, where &lt;span class="math"&gt;\(A \in \mathbb{R}^{m \times n}\)&lt;/span&gt;, &lt;span class="math"&gt;\(B \in \mathbb{R}^{n \times n}\)&lt;/span&gt; and &lt;span class="math"&gt;\(C \in \mathbb{R}^{n \times k}\)&lt;/span&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(m &amp;gt; k\)&lt;/span&gt;, we should compute &lt;span class="math"&gt;\(A (B^{-1} C)\)&lt;/span&gt;, which uses &lt;span class="math"&gt;\(\mathcal{O}( kn(m+n) )\)&lt;/span&gt; floating point operations.&lt;/li&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(k &amp;gt; m\)&lt;/span&gt;, we should compute &lt;span class="math"&gt;\((A B^{-1}) C\)&lt;/span&gt;, which uses &lt;span class="math"&gt;\(\mathcal{O}( mn(n +k) )\)&lt;/span&gt; floating point operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Below we solve the case when &lt;span class="math"&gt;\(k \gg m\)&lt;/span&gt; without forming the inverse.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&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;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100_000&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&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="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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&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="n"&gt;C&lt;/span&gt;  &lt;span class="c1"&gt;# 233 ms ± 56.4 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&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="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2.32 s ± 145 ms per loop&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&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;T&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;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;  &lt;span class="c1"&gt;# 157 ms ± 7.29 ms per loop&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&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="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2.71 s ± 174 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="dont-use-memory-you-dont-need"&gt;Don’t use memory you don’t need&lt;/h3&gt;
&lt;p&gt;If you can, use inplace operations to avoid filling up the memory with unnecessary arrays:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Mutate v without creating any new arrays&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Create a new array (v - 1) in memory, assign it to v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Inplace operations are also typically a bit faster. Win-win.&lt;/p&gt;
&lt;h3 id="dont-use-pure-python-loops"&gt;Don’t use pure Python loops&lt;/h3&gt;
&lt;p&gt;Avoid pure Python loops in numerical code.
If the obvious approach is to loop, first try to vectorize the computation.
If that fails, look into &lt;a href="https://numba.pydata.org/"&gt;Numba&lt;/a&gt; or &lt;a href="https://cython.org/"&gt;Cython&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here’s an example.
The code below computes the empirical cross-covariance between two data sets.
Each variable is a column, each row is a set of observations.&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="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;covariance_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&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="n"&gt;A&lt;/span&gt; &lt;span class="o"&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;np&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="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&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;B&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;np&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="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&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;cov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&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;shape&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="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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;j&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="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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;cov&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&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;cov&lt;/span&gt; &lt;span class="o"&gt;/&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;shape&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="mf"&gt;1.0&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;covariance_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&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="c1"&gt;# 3.62 s ± 241 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code above computes an outer product plus a matrix-matrix addition.
This is done for each row.&lt;/p&gt;
&lt;p&gt;Although the asymptotic complexity is the same, the code below is much faster because it eliminates the Python loop and expresses the computation as a single matrix-matrix product instead.&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="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;covariance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&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="n"&gt;A&lt;/span&gt; &lt;span class="o"&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;np&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="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&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;B&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;np&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="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&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;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&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="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;shape&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="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&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;A&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="c1"&gt;# 48.3 ms ± 8.75 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="do-use-the-woodbury-identity"&gt;Do use the Woodbury identity&lt;/h3&gt;
&lt;p&gt;Learn to use the &lt;a href="https://en.wikipedia.org/wiki/Woodbury_matrix_identity"&gt;Woodbury matrix identity&lt;/a&gt; and related formulas.
The Woodbury equation is:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\left(A + UCV \right)^{-1} = A^{-1} - A^{-1}U \left(C^{-1} + VA^{-1}U \right)^{-1} VA^{-1}
\end{equation}&lt;/div&gt;
&lt;p&gt;If &lt;span class="math"&gt;\(P\)&lt;/span&gt; and &lt;span class="math"&gt;\(R\)&lt;/span&gt; are positive definite, then the following holds:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\left(P^{-1} + B^T R^{-1} B \right)^{-1} B^T R^{-1} = P B^T (B P^T B + R)^{-1}
\end{equation}&lt;/div&gt;
&lt;p&gt;A typical use case is when we have inverted &lt;span class="math"&gt;\(A\)&lt;/span&gt;, and want to compute the inverse of a rank 1 update of &lt;span class="math"&gt;\(A\)&lt;/span&gt;.
In other words we wish to compute &lt;span class="math"&gt;\(\left(A + \boldsymbol{v}\boldsymbol{v}^T \right)^{-1}\)&lt;/span&gt;, and we already have &lt;span class="math"&gt;\(A^{-1}\)&lt;/span&gt;.
This is a special case of the Woodbury matrix identity, called the &lt;a href="https://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula"&gt;Sherman–Morrison formula&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;A_inv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inv&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 50.2 ms ± 6.32 ms per loop&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;rank1update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A_inv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&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;A_inv&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A_inv&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;A_inv&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;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;A_inv&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;rank1update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A_inv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 8.61 ms ± 2.9 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="do-think-about-what-the-meaning-is"&gt;Do think about what the meaning is&lt;/h3&gt;
&lt;p&gt;Just like &lt;span class="math"&gt;\(A \operatorname{diag}(\boldsymbol{v})\)&lt;/span&gt; simply scales the columns of &lt;span class="math"&gt;\(A\)&lt;/span&gt; with the entries of &lt;span class="math"&gt;\(\boldsymbol{v}\)&lt;/span&gt;, more tangled expressions occasionally have simple interpretations.
Consider the expression &lt;span class="math"&gt;\(A(I - \frac{1}{N}\boldsymbol{1}\boldsymbol{1}^T)\)&lt;/span&gt;.
Looks fancy, right?
It simply subtracts the mean from each row.
Instead of forming &lt;span class="math"&gt;\(I\)&lt;/span&gt;, then forming &lt;span class="math"&gt;\(\boldsymbol{1}\boldsymbol{1}^T\)&lt;/span&gt;, performing the subtraction, multiplying, etc—simply implement the row scaling directly.&lt;/p&gt;
&lt;p&gt;If you’re working in a thousand dimensions, its easy to get lost.
Work out simple examples in three or four dimensions before generalizing.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eye&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&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;factor&lt;/span&gt;  &lt;span class="c1"&gt;# 25 ms ± 9.58 ms per loop&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;np&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="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&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;keepdims&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 1.61 ms ± 60.4 µs per loop&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&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;factor&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;np&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="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&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;keepdims&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here’s another example from the wild.
I once saw someone implement&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cholesky&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&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="c1"&gt;# 724 ms ± 99.9 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;because that’s what a paper said.
But the Cholesky decomposition of a diagonal matrix is just the square of the diagonal, so the entire factorization is a waste of time—it does nothing at all.&lt;/p&gt;
&lt;h3 id="do-time-everything"&gt;Do time everything&lt;/h3&gt;
&lt;p&gt;We’ve been using the ubiquitous &lt;a href="https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit"&gt;&lt;code&gt;%timeit&lt;/code&gt;&lt;/a&gt; Ipython magic command in almost every example so far.
If you’re in doubt about whether one approach or the other is better, then implement both and time them.&lt;/p&gt;
&lt;p&gt;Consider solving the &lt;a href="https://en.wikipedia.org/wiki/Ridge_regression"&gt;Ridge regression&lt;/a&gt; optimization problem 
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\underset{\boldsymbol{\beta}}{\operatorname{minimize}} \quad \lVert X \boldsymbol{\beta} - \boldsymbol{y} \rVert^2_W + \lVert \boldsymbol{\beta}  \rVert^2,
\end{equation}&lt;/div&gt;
&lt;p&gt;
where &lt;span class="math"&gt;\(\lVert \boldsymbol{x}  \rVert^2_W = \boldsymbol{x}^T W \boldsymbol{x}\)&lt;/span&gt; denotes the weighted squared norm.
We assume that &lt;span class="math"&gt;\(W\)&lt;/span&gt; is a diagonal matrix with entries given by a vector &lt;span class="math"&gt;\(\boldsymbol{w}\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;We will examine three approaches to solving this problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The first approach&lt;/strong&gt; is to differentiate the objective function and set it equal to zero.
We obtain the &lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Ordinary_least_squares#Normal_equations"&gt;normal equations&lt;/a&gt;&lt;/em&gt; &lt;span class="math"&gt;\((X^T W X + I) \boldsymbol{\beta} = X^T W \boldsymbol{y}\)&lt;/span&gt;.
Forming the normal equations and solving could look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Solve |X @ beta - y|_w^2 + |beta|^2 for beta."""&lt;/span&gt;
    &lt;span class="n"&gt;XT_W&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="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;  &lt;span class="c1"&gt;# Form X.T @ diag(w)&lt;/span&gt;
    &lt;span class="n"&gt;lhs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XT_W&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;
    &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;[::&lt;/span&gt; &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Add identity&lt;/span&gt;
    &lt;span class="n"&gt;rhs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XT_W&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;assume_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 321 ms ± 97.2 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The second approach&lt;/strong&gt; is to realize that
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\lVert X \boldsymbol{\beta} - \boldsymbol{y} \rVert^2_W + \lVert \boldsymbol{\beta}  \rVert^2
= \lVert W^{1/2} X \boldsymbol{\beta} - W^{1/2} \boldsymbol{y} \rVert^2 + \lVert \boldsymbol{\beta}  \rVert^2
= \left\lVert  \begin{bmatrix}
W^{1/2} X \boldsymbol{\beta} - W^{1/2} \boldsymbol{y} \\
\boldsymbol{\beta} 
\end{bmatrix}  \right\rVert^2
= \left\lVert  \begin{bmatrix}
W^{1/2} X \\
I
\end{bmatrix} \boldsymbol{\beta}
-
\begin{bmatrix}
\boldsymbol{y} \\
\boldsymbol{0}
\end{bmatrix}
\right\rVert^2
\end{equation}&lt;/div&gt;
&lt;p&gt;
and make a call to a least squares solver with these matrices directly.&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="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve_lstsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Solve |X @ beta - y|_w^2 + |beta|^2 for beta."""&lt;/span&gt;
    &lt;span class="n"&gt;squared_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="n"&gt;lhs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;squared_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eye&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="n"&gt;rhs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hstack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;squared_w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="n"&gt;beta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lstsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&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;beta&lt;/span&gt;

&lt;span class="n"&gt;solve_lstlsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 1.34 s ± 429 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The third approach&lt;/strong&gt; uses the singular value decomposition (&lt;span class="caps"&gt;SVD&lt;/span&gt;). Let &lt;span class="math"&gt;\(W^{1/2}X = U D V^T\)&lt;/span&gt;, then the factor &lt;span class="math"&gt;\((X^T W X + I)\)&lt;/span&gt; in the normal equations may be written as
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
(X^T W X + I) = (W^{1/2}X)^T (W^{1/2}X) + I = (U D V^T)^T (U D V^T) + I = VD^2V^T + VV^T = V (D^2 + I) V^T
\end{equation}&lt;/div&gt;
&lt;p&gt;
and &lt;span class="math"&gt;\((X^T W X + I)^{-1} = V^T (D^2 + I)^{-1} V\)&lt;/span&gt;.
Plugging this back into the normal equations, we obtain the solution as
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\boldsymbol{\beta} = (X^T W X + I)^{-1} X^T W \boldsymbol{y} = V (D^2 + I)^{-1} D U^T W^{1/2} \boldsymbol{y}.
\end{equation}&lt;/div&gt;
&lt;p&gt;
In the implementation, we exploit the fact that &lt;span class="math"&gt;\((D^2 + I)^{-1}\)&lt;/span&gt;, &lt;span class="math"&gt;\(D\)&lt;/span&gt; and &lt;span class="math"&gt;\(W^{1/2}\)&lt;/span&gt; are diagonal matrices:&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="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve_svd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Solve |X @ beta - y|_w^2 + |beta|^2 for beta."""&lt;/span&gt;
    &lt;span class="n"&gt;w_sq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="n"&gt;W_sq_X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w_sq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;  &lt;span class="c1"&gt;# Form W^1/2 @ X&lt;/span&gt;
    &lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;svd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;W_sq_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_matrices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overwrite_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inv_diag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;/&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;d&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="c1"&gt;# Form (D^2 + I)^-1 @ D&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multi_dot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;VT&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;inv_diag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;U&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w_sq&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

&lt;span class="n"&gt;solve_svd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 2.47 s ± 329 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The approaches return the same answer:&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;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;solve_lstsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;solve_svd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The third approach using the &lt;span class="caps"&gt;SVD&lt;/span&gt; is what you might find in a paper.
However, &lt;a href="https://github.com/scikit-learn/scikit-learn/blob/7f9bad99d6e0a3e8ddf92a7e5561245224dab102/sklearn/linear_model/_ridge.py#L211C17-L211C17"&gt;sklearn implements approach one&lt;/a&gt;, which is significantly faster.
Under the hood, &lt;a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.solve.html"&gt;&lt;code&gt;scipy.linalg.solve&lt;/code&gt;&lt;/a&gt; calls the &lt;span class="caps"&gt;LAPACK&lt;/span&gt; routine &lt;a href="https://netlib.org/lapack/explore-html/de/d80/group__real_p_osolve_gad46400c796afc8031a8c3ec5481af8c3.html"&gt;&lt;code&gt;sposv&lt;/code&gt;&lt;/a&gt;, which uses a Cholesky decomposition.
It’s typically instructive to implement a few different approaches, then test them and time them.
We have not discussed the numerical stability of the least-squares problem here, but have a look at the references at the end of this article for more information about that issue.&lt;/p&gt;
&lt;h3 id="do-read-the-documentation"&gt;Do read the documentation&lt;/h3&gt;
&lt;p&gt;Consider again the function &lt;code&gt;solve_normal_eqns(X, w, y)&lt;/code&gt; that we used to solve the normal equations &lt;span class="math"&gt;\((X^T W X + I) \boldsymbol{\beta} = X^T W \boldsymbol{y}\)&lt;/span&gt; with.
It turns out that &lt;a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.solve.html"&gt;&lt;code&gt;scipy.linalg.solve(a, b)&lt;/code&gt;&lt;/a&gt; has a parameter &lt;code&gt;lower=False&lt;/code&gt;, and the documentation reads:&lt;/p&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;If &lt;code&gt;False&lt;/code&gt; (default), the calculation uses only the data in the upper triangle of &lt;code&gt;a&lt;/code&gt;; entries below the diagonal are ignored.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It also turns out that there’s a &lt;span class="caps"&gt;BLAS&lt;/span&gt; routine with the cryptic name &lt;a href="https://netlib.org/lapack/explore-html/d1/d54/group__double__blas__level3_gae0ba56279ae3fa27c75fefbc4cc73ddf.html#gae0ba56279ae3fa27c75fefbc4cc73ddf"&gt;&lt;code&gt;dsyrk&lt;/code&gt;&lt;/a&gt; that only computes the upper-triangular part of &lt;span class="math"&gt;\(\alpha A A^T\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;We can improve the speed of our solver by only computing the upper-triangular part of &lt;span class="math"&gt;\(X^T W X + I\)&lt;/span&gt; as follows:&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="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve_normal_eqns2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Solve |X @ beta - y|_w^2 + |beta|^2 for beta."""&lt;/span&gt;

    &lt;span class="c1"&gt;# Compute upper triangular entries in X.T @ diag(w) @ X&lt;/span&gt;
    &lt;span class="n"&gt;XT_W_X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blas&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dsyrk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&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;a&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="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Add identity matrix&lt;/span&gt;
    &lt;span class="n"&gt;XT_W_X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="p"&gt;[::&lt;/span&gt; &lt;span class="n"&gt;XT_W_X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;XT_W_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;assume_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Solver only uses upper triangular elements&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;overwrite_b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 4.78 s ± 393 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;solve_normal_eqns2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 3.33 s ± 436 ms per loop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you’re working with linear algebra, familiarize yourself with all the functions in &lt;a href="https://docs.scipy.org/doc/scipy/reference/linalg.html"&gt;scipy.linalg&lt;/a&gt;.
When using a function, read carefully through the arguments.
If speed and accuracy truly matters, look into how functions are implemented and research the specific problem you’re working on carefully.&lt;/p&gt;
&lt;h3 id="do-consider-approximations"&gt;Do consider approximations&lt;/h3&gt;
&lt;p&gt;Sometimes we don’t need exact answers.
For instance, we can approximately solve the Ridge regression problem by running five Conjugate Gradient iterations on the normal equations.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;XT_W&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="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;matvec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Implements (X.T @ diag(v) @ X + I) @ v&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multi_dot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;XT_W&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;v&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;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LinearOperator&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="n"&gt;X&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="n"&gt;matvec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;matvec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 56.9 ms ± 13 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;approx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;XT_W&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 295 ms ± 55.1 ms per loop&lt;/span&gt;
&lt;span class="n"&gt;exact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;solve_normal_eqns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The Pearson correlation between the exact and approximate answer is approximately &lt;span class="math"&gt;\(0.998\)&lt;/span&gt;.
What we gain in speed might make up for the loss of accuracy, but this is highly problem dependent.
An approximate singular value decomposition can be obtained using &lt;a href="https://scikit-learn.org/stable/modules/generated/sklearn.utils.extmath.randomized_svd.html"&gt;&lt;code&gt;sklearn.utils.extmath.randomized_svd&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="resources-and-further-reading"&gt;Resources and further reading&lt;/h2&gt;
&lt;p&gt;Three excellent books:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Numerical-Linear-Algebra-Lloyd-Trefethen/dp/0898713617"&gt;Numerical Linear Algebra&lt;/a&gt; by Trefethen et al (1997).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Computations-Hopkins-Studies-Mathematical-Sciences/dp/1421407949"&gt;Matrix Computations&lt;/a&gt; by Golub et al (2013).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Accuracy-Stability-Numerical-Algorithms-Nicholas/dp/0898715210/"&gt;Accuracy and Stability of Numerical Algorithms&lt;/a&gt; by Nicholas J. Higham (2002).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two reference manuals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.ee.ic.ac.uk/hp/staff/dmb/matrix/intro.html"&gt;The Matrix Reference Manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf"&gt;The Matrix Cookbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An open-source book on vectorization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.labri.fr/perso/nrougier/from-python-to-numpy/"&gt;From Python to Numpy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 30 Jan 2026 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2026-01-30:/articles/2026/practical-linear-algebra-with-numpy</guid><category>articles</category><category>mathematics</category><category>algorithms</category></item><item><title>Books</title><link>https://tommyodland.com/articles/2025/books</link><description>&lt;p&gt;Here’s an inspirational, and perhaps true, quote by &lt;a href="https://youtu.be/U41e7hKAAPQ?t=5710"&gt;Stephen Boyd&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;When you end up being trained in just a handful of areas, you can be unbelievably effective across fifty fields.
If you know linear algebra, optimization, probability and statistics, and computer science – those are the topics.
You don’t have to know anything else.&lt;/p&gt;
&lt;p&gt;If you know those things, and really know them, and if you’ve seen a bunch of applications across different fields, you are so valuable it’s crazy.
You can be hired instantly into a hundred fields.
But here’s the even cooler thing, and this is the Silicon Valley part of it: you can create new fields that don’t even exist.
Please train yourself broadly.
Learn these things.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr/&gt;
&lt;p&gt;Below is a reading list for aspiring data scientists and others interested in mathematical modeling, statistics, optimization, scientific programming, etc.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only my favorite books are included. Books I would recommend.&lt;/li&gt;
&lt;li&gt;Only general topics useful to many practitioners are included. Category theory, game theory and geostatistics is cool – but not &lt;em&gt;that&lt;/em&gt; useful to the average practitioner.&lt;/li&gt;
&lt;li&gt;I have read the majority of the material, but not everything.&lt;/li&gt;
&lt;li&gt;The resources are sorted by difficulty within reach category, but only roughly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The nomenclature is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📗 Book.&lt;/li&gt;
&lt;li&gt;📄 Paper.&lt;/li&gt;
&lt;li&gt;📺 Video lecture(s).&lt;/li&gt;
&lt;li&gt;🤖 Book contains many code examples.&lt;/li&gt;
&lt;li&gt;⭐ Life changing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mathematics"&gt;Mathematics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Introduction-Applied-Linear-Algebra-Matrices/dp/1316518965"&gt;Introduction to Applied Linear Algebra: Vectors, Matrices, and Least Squares&lt;/a&gt; by Boyd et al (2018).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Advanced-Engineering-Mathematics-Michael-Greenberg/dp/0133214311"&gt;Advanced Engineering Mathematics&lt;/a&gt; by Michael Greenberg (1998).&lt;/li&gt;
&lt;li&gt;📗 ⭐ &lt;a href="https://www.amazon.com/Introduction-Applied-Mathematics-Gilbert-Strang/dp/0961408804"&gt;Introduction to Applied Mathematics&lt;/a&gt; by Gilbert Strang (1986). &lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Princeton-Companion-Mathematics-Timothy-Gowers/dp/0691118809"&gt;The Princeton Companion to Mathematics&lt;/a&gt; by Gowers et al (2008).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf"&gt;An Introduction to the Conjugate Gradient Method Without the Agonizing Pain&lt;/a&gt; by Jonathan Richard Shewchuk (1994).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="scientific-computing"&gt;Scientific computing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Numerical-Methods-Scientists-Engineers-Mathematics/dp/0486652416/"&gt;Numerical Methods for Scientists and Engineers&lt;/a&gt; by &lt;span class="caps"&gt;R. W.&lt;/span&gt; Hamming (1987).&lt;/li&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://www.labri.fr/perso/nrougier/from-python-to-numpy/"&gt;From Python to Numpy&lt;/a&gt; by Nicolas P. Rougier (2017).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Numerical-Linear-Algebra-Lloyd-Trefethen/dp/0898713617/"&gt;Numerical Linear Algebra&lt;/a&gt; by Trefethen et al (1997).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Computations-Hopkins-Studies-Mathematical-Sciences/dp/1421407949/"&gt;Matrix Computations&lt;/a&gt; by Golub et al (2013).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="python-and-programming"&gt;Python and programming&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Pragmatic-Programmer-Journeyman-Master/dp/020161622X"&gt;The Pragmatic Programmer&lt;/a&gt; by Hunt et al (2019).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Philosophy-Software-Design-2nd/dp/173210221X"&gt;A Philosophy of Software Design&lt;/a&gt; by John Ousterhout (2021).&lt;/li&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://www.amazon.com/Fluent-Python-Concise-Effective-Programming/dp/1491946008/"&gt;Fluent Python: Clear, Concise, and Effective Programming&lt;/a&gt; by Luciano Ramalho (2015).&lt;/li&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://www.amazon.com/Effective-Python-Specific-Software-Development/dp/0134853989/"&gt;Effective Python: 90 Specific Ways to Write Better Python&lt;/a&gt; by Brett Slatkin (2019).&lt;/li&gt;
&lt;li&gt;📺 &lt;a href="https://www.youtube.com/watch?v=cKPlPJyQrt4"&gt;So you want to be a Python expert?&lt;/a&gt; by James Powell (2017).&lt;/li&gt;
&lt;li&gt;📺 &lt;a href="https://www.youtube.com/watch?v=OSGv2VnC0go"&gt;Transforming Code into Beautiful, Idiomatic Python&lt;/a&gt; by Raymond Hettinger (2013).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://doi.org/10.1371/journal.pcbi.1005510"&gt;Good enough practices in scientific computing&lt;/a&gt; by Wilson et al (2017).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://www.cs.unc.edu/techreports/86-020.pdf"&gt;No Silver Bullet - Essence and Accident in Software Engineering&lt;/a&gt; by Fred Brooks (1986).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="machine-learning"&gt;Machine learning&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Artificial-Intelligence-Modern-Approach-3rd/dp/0136042597/"&gt;Artificial Intelligence: A Modern Approach&lt;/a&gt; by Norvig et al (2009).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Pattern-Classification-Pt-1-Richard-Duda/dp/0471056693/"&gt;Pattern Classification&lt;/a&gt; by Duda et al (2000).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Pattern-Recognition-Learning-Information-Statistics/dp/0387310738/"&gt;Pattern Recognition and Machine Learning&lt;/a&gt; by Christopher M. Bishop (2006).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Elements-Statistical-Learning-Prediction-Statistics/dp/0387848576/"&gt;The Elements of Statistical Learning&lt;/a&gt; by Hastie et al (2016).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://sites.astro.caltech.edu/~george/ay122/cacm12.pdf"&gt;A Few Useful Things to Know about Machine Learning&lt;/a&gt; by Pedro Domingos (2012).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://arxiv.org/abs/1811.10154"&gt;Stop Explaining Black Box Machine Learning Models for High Stakes Decisions and Use Interpretable Models Instead&lt;/a&gt; by Cynthia Rudin (2018).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://ieeexplore.ieee.org/document/5694074"&gt;Factorization Machines&lt;/a&gt; by Steffen Rendle (2010).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://research.google/pubs/pub43146/"&gt;Machine Learning: The High Interest Credit Card of Technical Debt&lt;/a&gt; by Sculley et al (2014).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="deep-learning-etc"&gt;Deep learning etc.&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://www.amazon.com/Hands-Machine-Learning-Scikit-Learn-TensorFlow/dp/1098125975/"&gt;Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow&lt;/a&gt; by Aurélien Géron (2022).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Reinforcement-Learning-Introduction-Adaptive-Computation/dp/0262039249/"&gt;Reinforcement Learning&lt;/a&gt; by Sutton (2018).&lt;/li&gt;
&lt;li&gt;📺 &lt;a href="https://www.youtube.com/watch?v=2pWv7GOvuf0&amp;amp;list=PLqYmG7hTraZDM-OYHWgPebj2MfCFzFObQ"&gt;DeepMind x &lt;span class="caps"&gt;UCL&lt;/span&gt; | Reinforcement Learning Course&lt;/a&gt; by David Silver (2015).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="statistics"&gt;Statistics&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Statistical-Inference-George-Casella/dp/8131503941/"&gt;Statistical Inference&lt;/a&gt; by Casella et al (2001).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Regression-Stories-Analytical-Methods-Research/dp/1107676517/"&gt;Regression and Other Stories&lt;/a&gt; by Gelman et al (2020).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Analysis-Regression-Multilevel-Hierarchical-Analytical-ebook/dp/B01LYX8AKU/"&gt;Data Analysis Using Regression and Hierarchical Models&lt;/a&gt; by Gelman et al (2006).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/All-Statistics-Statistical-Inference-Springer/dp/1441923225/"&gt;All of Statistics&lt;/a&gt; by Larry Wasserman (2010).&lt;/li&gt;
&lt;li&gt;📗 🤖 ⭐ &lt;a href="https://www.amazon.com/Statistical-Rethinking-Bayesian-Examples-Chapman/dp/036713991X/"&gt;Statistical Rethinking&lt;/a&gt; by Richard McElreath (2020).&lt;/li&gt;
&lt;li&gt;📺 &lt;a href="https://www.youtube.com/watch?v=BYUykHScxj8&amp;amp;list=PLDcUM9US4XdMROZ57-OIRtIK0aOynbgZN"&gt;Statistical Rethinking&lt;/a&gt; by Richard McElreath (2022).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://www.semanticscholar.org/paper/The-garden-of-forking-paths-%3A-Why-multiple-can-be-a-Gelman-Loken/b63e25900013605c16f4ad74c636cfbd8e9a3e8e"&gt;The garden of forking paths&lt;/a&gt; by Gelman et al (2019).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://projecteuclid.org/journals/statistical-science/volume-16/issue-3/Statistical-Modeling--The-Two-Cultures-with-comments-and-a/10.1214/ss/1009213726.full"&gt;Statistical Modeling: The Two Cultures&lt;/a&gt; by Leo Breiman (2001).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://peerj.com/articles/6876/"&gt;Hierarchical generalized additive models in ecology&lt;/a&gt; by Pedersen et al (2019).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://arxiv.org/abs/2012.00174"&gt;What are the most important statistical ideas of the past 50 years?&lt;/a&gt; by Gelman et al (2021).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://arxiv.org/abs/1701.02434"&gt;A Conceptual Introduction to Hamiltonian Monte Carlo&lt;/a&gt; by Michael Betancourt (2018).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="optimization"&gt;Optimization&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Basics-Practical-Optimization-Second/dp/1611977363/"&gt;The Basics of Practical Optimization&lt;/a&gt; by Adam B. Levy (2022).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Optimization-Modelling-Ruhul-Amin-Sarker-ebook/dp/B008KZ6MK4/"&gt;Optimization Modelling: A Practical Approach&lt;/a&gt; by Sarker et al (2007).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Essentials-Metaheuristics-Second-Sean-Luke/dp/1300549629/"&gt;Essentials of Metaheuristics&lt;/a&gt; by Sean Luke (2012).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Linear-Programming-Foundations-Extensions-International/dp/3030394174/"&gt;Linear Programming: Foundations and Extensions&lt;/a&gt; by Robert J. Vanderbei (2021).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Convex-Optimization-Corrections-2008-Stephen/dp/0521833787/"&gt;Convex Optimization&lt;/a&gt; by Boyd et al (2004).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Numerical-Optimization-Operations-Financial-Engineering/dp/0387303030/"&gt;Numerical Optimization&lt;/a&gt; by Nocedal et al (2006).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://pubsonline.informs.org/doi/10.1287/ited.7.2.153"&gt;Formulating Integer Linear Programs: A Rogues’ Gallery&lt;/a&gt; by Brown et al (2007).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://pubsonline.informs.org/doi/abs/10.1287/inte.2019.1015?journalCode=ijaa"&gt;Bus Routing Optimization Helps Boston Public Schools Design Better Policies&lt;/a&gt; by Bertsimas et al (2020).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="algorithms"&gt;Algorithms&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Algorithm-Design-Manual-Computer-Science/dp/3030542580/"&gt;The Algorithm Design Manual&lt;/a&gt; by Steven S. Skiena (2021).&lt;/li&gt;
&lt;li&gt;📗 ⭐ &lt;a href="https://www.amazon.com/Algorithms-Sanjoy-Dasgupta/dp/0073523402/"&gt;Algorithms&lt;/a&gt; by Dasgupta et al (2006).&lt;/li&gt;
&lt;li&gt;📗 🤖 &lt;a href="https://cpbook.net/"&gt;Competitive Programming&lt;/a&gt; by Halim et al.&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Introduction-Algorithms-fourth-Thomas-Cormen/dp/026204630X/"&gt;Introduction to Algorithms&lt;/a&gt; by Cormen et al (2022).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Network-Flows-Theory-Algorithms-Applications/dp/013617549X/"&gt;Network Flows: Theory, Algorithms, and Applications&lt;/a&gt; by Ahuja et al (1993).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="popular-science"&gt;Popular science&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Flaws-Fallacies-Statistical-Thinking-Mathematics-ebook/dp/B00A62Y1X4/"&gt;Flaws and Fallacies in Statistical Thinking&lt;/a&gt; by Stephen K. Campbell (2012).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Innumeracy-Mathematical-Illiteracy-Its-Consequences/dp/0809058405/"&gt;Innumeracy: Mathematical Illiteracy and Its Consequences&lt;/a&gt; by  John Allen Paulos (2001).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Science-Fictions-Negligence-Undermine-Search/dp/1250222699"&gt;Science Fictions&lt;/a&gt; by Stuart Ritchie (2020). &lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Concepts-Modern-Mathematics-Dover-Books/dp/0486284247/"&gt;Concepts of Modern Mathematics&lt;/a&gt; by Ian Stewart (1995).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Euler-Master-Dolciani-Mathematical-Expositions/dp/0883853280"&gt;Euler: The Master of Us All&lt;/a&gt; by William Dunham (1999).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Book-Why-Science-Cause-Effect-ebook/dp/B075CR9QBJ/"&gt;The Book of Why&lt;/a&gt; by Judea Pearl et al (2018).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="miscellaneous"&gt;Miscellaneous&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/How-Big-Things-Get-Done/dp/0593239512"&gt;How Big Things Get Done&lt;/a&gt; by Flyvbjerg (2023).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Visual-Display-Quantitative-Information/dp/0961392142/"&gt;The Visual Display of Quantitative Information&lt;/a&gt; by Edward R. Tufte (2001).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Writing-Well-Classic-Guide-Nonfiction/dp/0060891548/"&gt;On Writing Well: The Classic Guide to Writing Nonfiction&lt;/a&gt; by William Zinsser (2016).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Handbook-Writing-Mathematical-Sciences-Nicholas/dp/161197609X/"&gt;Handbook of Writing for the Mathematical Sciences&lt;/a&gt; by Nicholas J. Higham (2019). &lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/How-Solve-Mathematical-Princeton-Science/dp/069116407X/"&gt;How to Solve It&lt;/a&gt; by Polya et al (2014).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf"&gt;The Matrix Cookbook&lt;/a&gt; by Pedersen et al (2012).&lt;/li&gt;
&lt;li&gt;📗 &lt;a href="https://www.amazon.com/Opt-Art-Mathematical-Optimization-Visual/dp/0691164061"&gt;Opt Art: From Mathematical Optimization to Visual Design&lt;/a&gt; by Robert Bosch (2019).&lt;/li&gt;
&lt;li&gt;📄 &lt;a href="https://www.jstor.org/stable/3132114"&gt;School Choice: A Mechanism Design Approach&lt;/a&gt; by Abdulkadiroğlu et al (2003).&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 15 Dec 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-12-15:/articles/2025/books</guid><category>articles</category><category>datascience</category></item><item><title>Fastlegebytter</title><link>https://tommyodland.com/articles/2025/fastlegebytter</link><description>&lt;p&gt;&lt;strong&gt;Oppdatering&lt;/strong&gt;: To dager etter at jeg skrev denne artikkelen ble jeg informert om innlegget “&lt;a href="https://www.dn.no/innlegg/helse/leger/fastleger/enkelt-a-forbedre-fastlegeordningen-med-en-algoritme/2-1-1692543"&gt;Enkelt å forbedre fastlegeordningen - med en algoritme&lt;/a&gt;” i Dagens Næringsliv.
Forfatterne argumenterer for mye av det samme som det jeg gjør nedenfor.
Innlegget deres er veldig godt skrevet, og de har også skrevet en 80-siders forskningsartikkel “&lt;a href="https://www.nber.org/papers/w32458"&gt;Designing Dynamic Reassignment Mechanisms: Evidence from &lt;span class="caps"&gt;GP&lt;/span&gt; Allocation&lt;/a&gt;”.
De beregner at 15 % (ca. 45 000 mennesker) kunne sluppet venteliste ved å endre på algoritmen!&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Det er ikke uvanlig å vente i årevis på &lt;a href="https://www.helsenorge.no/nn/bytte-fastlege/om/"&gt;fastlegebytte&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I 2024 kunne vi lese at &lt;a href="https://www.regjeringen.no/no/aktuelt/flere-fastleger/id3024659/"&gt;over 300 000 står på venteliste&lt;/a&gt;.
På &lt;a href="https://www.helsenorge.no/"&gt;helsenorge.no&lt;/a&gt; er det 608 fastleger registrert i Oslo fylke, og 60 % av disse har fulle lister.
En lege har typisk plass til 1 300 pasienter, og det er 816 000 plasser i Oslo fylke totalt.
Kun 4,5 % av disse plassene er ledige.
Situasjonen er enda verre andre steder i landet.
I 2022 var det &lt;a href="https://www.legelisten.no/blogg/100-lange-ventetider-for-a-bytte-fastlege"&gt;ille i Rogaland&lt;/a&gt;, der kun 3 % av fastlegene hadde ledige plasser.
I Bergen og Trondheim var det ikke en eneste ledig plass.&lt;/p&gt;
&lt;p&gt;Et stort problem med fulle lister er at &lt;strong&gt;dagens ordning ikke tillater bytter mellom pasienter på fulle lister. Dette er ineffektivt fordi det låser markedet.&lt;/strong&gt;
I denne artikkelen skal jeg forklare dette utsagnet og foreslå en løsning på problemet.&lt;/p&gt;
&lt;h2 id="bytter-mellom-fulle-lister"&gt;Bytter mellom fulle lister&lt;/h2&gt;
&lt;p&gt;Her er et eksempel på en situasjon der vi kunne kuttet ventetiden drastisk.
Ola er på lista til lege A.
Kari er på lista til lege B.
Ola ønsker å bytte fra lege A til lege B.
Kari ønsker å bytte motsatt vei; fra lege B til lege A.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fastlegebytter/fastlegebytte_fulle_lister.png" style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width: 700px;"/&gt;&lt;/p&gt;
&lt;p&gt;I dag må noen forlate listen til én av legene før Ola, Kari eller Per får innvilget søknad om fastlegebytte.
Men Ola og Kari hadde ikke trengt å vente (potensielt i årevis) - &lt;strong&gt;de kunne ha byttet plass i dag&lt;/strong&gt;!
Vi trenger bare en algoritme som leter etter sykluser i bytte-grafen og gjennomfører byttene.&lt;/p&gt;
&lt;p&gt;Er du bekymret for Per i figuren ovenfor?
Den nye algoritmen er bedre for både Ola og Kari, men Per har potensielt stått i kø lengre enn Ola.
Kan Per havne i en situasjon der han må vente lengre enn med dagens algoritme?
Svaret er ja - det er mulig å konstruere et slikt eksempel:&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fastlegebytter/fastlegebytte_fulle_lister_rekkefølge.png" style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width: 700px;"/&gt;&lt;/p&gt;
&lt;p&gt;Anta at pasientene melder ønske om bytte i rekkefølgen Kari, Per og Ola.
Vi står i en fastlåst situasjon som vist i figuren ovenfor, der ingen får bytte fordi det ikke er ledige plasser.
Om noen dager vil lege A få en ledig plass, men det vet vi ikke enda.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dagens algoritme.&lt;/strong&gt; Dersom vi ikke kjører syklus-algoritmen er situasjonen fastlåst frem til lege A får en ledig plass. Da får Kari sitt ønske innvilget ettefulgt av Per, mens Ola står fremdeles i kø.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Foreslått algoritme.&lt;/strong&gt; Dersom vi kjører syklus-algoritmen bytter Ola og Kari umiddelbart plass. Når lege A får en ledig plass påvirker det ikke Per. Han står fremdeles i kø.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Per må vente til lege B får en ledig plass som ikke umiddelbart fylles fordi den er del av en bytte-syklus.
Oppsummert kan det å gjennomføre sykluser-bytter mellom fulle lister fjerne (potensielt lange) stier i grafen som ville innfridd flere ønsker.&lt;/p&gt;
&lt;p&gt;Er dette eksempelet et godt argument mot den foreslåtten algoritmen?
Jeg mener at det ikke er det.
Per er kanskje misfornøyd dersom virkeligheten utspiller seg som ovenfor, men langt flere er misfornøyde med dagens regime.
Realiteten er at ingen på venteliste vet om de er en del av en syklus (slik som Ola og Kari) eller ikke (slik som Per), og mange bytter ødelegger ikke for personer som Per.
Markedet er i dag helt låst i byer som Bergen, der kun 3 av 274 fastleger har ledige plasser.
I slike situasjoner ville syklus-bytter betydd mye for pasientene.&lt;/p&gt;
&lt;h2 id="lengre-sykluser"&gt;Lengre sykluser&lt;/h2&gt;
&lt;p&gt;Ovenfor byttet vi plass på to personer: Ola og Kari.
Vi kan bytte plass på et vilkårlig antall personer så lenge de lager en syklus i bytte-grafen.&lt;/p&gt;
&lt;p&gt;Av og til må vi ta et valg, fordi én persons bytteønske kan brukes i flere sykluser, slik som i figuren nedenfor.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fastlegebytter/fastlegebytte_cycle.png" style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width: 350px;"/&gt;&lt;/p&gt;
&lt;p&gt;I slike situasjoner kan vi enten velge den lengste syklusen (grønn), eller vi kan velge den syklusen der pasientene har ventet lengst på å få bytte.
Det er et verdispørsmål, ikke et matematisk spørsmål - og uansett hva vi velger blir utfallet bedre enn i dag for de aller fleste.&lt;/p&gt;
&lt;p&gt;Et annet valg vi må ta er om vi utfører syklus-bytter ofte (f.eks. hver dag) eller sjeldent (f.eks. hver måned).
Å bytte ofte gir umiddelbar bevegelse i markedet.
Å bytte litt mer sjeldent kan føre til at det bygger seg opp lengre syklusen i grafen som vi kan utnytte.
En siste idé er å tillate mer enn ett ønske: hvorfor ikke la pasientene rangere flere leger som de kunne tenke seg å bytte til?&lt;/p&gt;
&lt;h2 id="oppsummering-og-referanser"&gt;Oppsummering og referanser&lt;/h2&gt;
&lt;p&gt;På tross av målsetninger om å digitalisere og effektivisere helsesektoren, er det fremdeles noen frukter som henger så lavt at de nesten tar i bakken.
Syklus-bytter hjelper ikke de som er villige til å stå i kø i fem år for å være pastient hos den beste legen i byen.
Men det hjelper når Ola flytter fra en bydel til en annen og vil bytte lege, mens Kari flytter motsatt vei.
Algoritmer som baserer seg på å fjerne sykluser har vært kjent siden 70-tallet, da &lt;a href="https://en.wikipedia.org/wiki/Top_trading_cycle"&gt;Top Trading Cycle&lt;/a&gt; (&lt;span class="caps"&gt;TTC&lt;/span&gt;) først ble publisert.
Selv om &lt;span class="caps"&gt;TTC&lt;/span&gt; er Pareto-effektiv, gir den ikke nødvendigvis de lengste syklusene.
&lt;a href="https://en.wikipedia.org/wiki/Optimal_kidney_exchange"&gt;Optimal kidney exchange&lt;/a&gt; handler om å finne de lengste syklusene.&lt;/p&gt;
&lt;p&gt;Boka &lt;a href="https://www.amazon.com/Who-Gets-What-Why-Matchmaking/dp/0544705289"&gt;Who Gets What - and Why: The New Economics of Matchmaking and Market Design&lt;/a&gt; av Alvin Roth er en folkelig introduksjon til markeder uten penger og uten delelige varer.
Fagfeltet heter &lt;em&gt;mekanismedesign&lt;/em&gt;, og er godt kjent gjennom Wikipedia-artikler, bøker og en Nobelpris til Roth i 2012.
På tross av dette er denne kompetansen tilsynelatende lav, både i offentlig og privat sektor.
Dette skrev jeg også om i artikkelen “&lt;a href="https://tommyodland.com/files/storage/bedre_skole_nr3_2022_odland_murray.pdf"&gt;Mangelfull evaluering av inntaksmodeller&lt;/a&gt;”, der jeg argumenterte mot å bruke politikernes hjemmesnekra modeller til å avgjøre skjebnen til tusenvis at &lt;span class="caps"&gt;VGS&lt;/span&gt;-elever.&lt;/p&gt;
&lt;p&gt;De fleste overvurderer betydningen av sitt eget fagfelt.
Jeg er neppe noe unntak, fordi jeg tror matematikk kan spille en rolle i en effektiv helsesektor.
&lt;span class="caps"&gt;PS&lt;/span&gt;: Les gjerne artikkelen min om &lt;a href="https://tommyodland.com/articles/2024/turnusplaner-for-sykepleiere"&gt;turnusplaner for sykepleiere&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="korrespondanse-med-norsk-helsenett-sf"&gt;Korrespondanse med Norsk helsenett &lt;span class="caps"&gt;SF&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Jeg kontaktet Norsk helsenett &lt;span class="caps"&gt;SF&lt;/span&gt; (Helsenorge) om problemstillingen.
Ut fra svaret tolker jeg at denne artikkelen fremstiller dagens system riktig.&lt;/p&gt;
&lt;p&gt;For meg virker det som en åpenbar forbedring å tillate bytter, og jeg vet ikke hvorfor Helsenorge ikke gjør det.
Det kan hende de har gode grunner til å beholde dagens system, eller at de har andre prioriteringer, eller at de har &lt;span class="caps"&gt;IT&lt;/span&gt;-systemer som er vanskelige å endre, eller mangler kompetanse til å implementere en slik algoritme.&lt;/p&gt;
&lt;p&gt;Her er korrespondansen:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Hei,

Dersom to (eller flere) personer innbyrdes ønsker å bytte fastlege, 
skjer byttet umiddelbart?

Eksempel: Ola har A som fastlege. Kari har B som fastlege. 
Både A og B har helt fulle lister. Ola ønsker å bytte fra A til B. 
Kari ønsker å bytte fra B til A. Ingen andre som står til kø til verken 
A eller B må vente lengre dersom Ola og Kari får bytte umiddelbart. 
Er algoritmen smart nok til å gjøre slike bytter, eller må man vente
til en på en ledig plass fordi noen først forlater listene?

// Tommy

--------------------------------

Hei,

Det er dessverre ikke mulig å bytte plass med noen. 
Dersom ønsket lege ikke har ledig plass, må man stå på venteliste.

Vennlig hilsen
Veiledning Helsenorge
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Wed, 05 Nov 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-11-05:/articles/2025/fastlegebytter</guid><category>articles</category><category>mathematics</category></item><item><title>Monte Carlo modeling in Python with probabilit</title><link>https://tommyodland.com/articles/2025/monte-carlo-modeling-in-python-with-probabilit</link><description>
&lt;main&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=5ed59cbb-644c-4b4b-b97a-f7d39f762163"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/equinor/probabilit"&gt;probabilit&lt;/a&gt; is a Python package for Monte Carlo modeling&lt;/strong&gt; that I helped create.
It’s great for prototyping uncertainty calculations in some types of problems, such as those encountered in undergraduate engineering courses.
In addition to a high-level modeling language, it contains algorithms for inducing correlations between variables, as well as computing the nearest correlation matrix.&lt;/p&gt;
&lt;p&gt;Here is what probabilit can do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has a high-level modeling language that lets us model equations and expressions using probability distributions&lt;/li&gt;
&lt;li&gt;It supports quasi Monte Carlo sampling: Latin Hypercube, Sobol, and Halton sequences&lt;/li&gt;
&lt;li&gt;It can correlate variables, e.g. induce a correlation between a uniform and a normal distribution&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is what probabilit does not do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s not suited for modeling complex environments, like multi-agent systems, queues, differential equations, etc.&lt;/li&gt;
&lt;li&gt;It’s not as memory efficient nor as fast as writing pure numpy simulations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This article is a tutorial demonstrating the most important features of &lt;a href="https://github.com/equinor/probabilit"&gt;probabilit&lt;/a&gt;.
We’ll be using version 0.1.0.
Beware that in future versions some things might change slightly.
However, the overall ideas will remain the same, and all the Python code in this tutorial is executable (it was created from a jupyter notebook).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell jp-mod-noOutputs" id="cell-id=771a9dcb-5706-4cb1-9258-2a6ce94e394a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [1]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=44f030f5-4157-435f-bee8-8afa16d04f01"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h1 id="Monte-Carlo-modeling-examples"&gt;Monte Carlo modeling examples&lt;a class="anchor-link" href="#Monte-Carlo-modeling-examples"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Let us jump into probabilit with some modeling examples.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=65a7a787-aa01-49a1-83ca-23150fc1a18e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Braking-distance-of-a-car"&gt;Braking distance of a car&lt;a class="anchor-link" href="#Braking-distance-of-a-car"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=e110d710-5b15-4fdb-a231-7cf9887f5c22"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;.
Suppose we’re driving down the highway at approximately 100 km/h when suddenly a family of ducks appear and we have to slam the brakes.
What is the braking distance?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deterministic answer&lt;/strong&gt;.
It turns out that the relevant equation, obtained by realizing that the braking energy must cancel out the kinetic energy, is &lt;code&gt;d = v^2 / (2 * mu * g)&lt;/code&gt;, where&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;d&lt;/code&gt; = braking distance (m)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v&lt;/code&gt; = velocity (m/s)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mu&lt;/code&gt; = &lt;a href="https://en.wikibooks.org/wiki/Physics_Study_Guide/Frictional_coefficients"&gt;coefficient of friction&lt;/a&gt; (dimensionless)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;g&lt;/code&gt; = gravitational acceleration (m/s²)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A deterministic answer is straightforward to compute in Python:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=5a863c5f-e926-4e99-a222-673da1c94d8a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [2]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;braking_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity_kmh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;9.81&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;velocity_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;velocity_kmh&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;3.6&lt;/span&gt;  &lt;span class="c1"&gt;# Convert to m/s&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;velocity_ms&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;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;velocity_kmh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;mu_dry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;  &lt;span class="c1"&gt;# Coefficient of friction on dry road&lt;/span&gt;
&lt;span class="n"&gt;distance_dry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;braking_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity_kmh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu_dry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;distance_dry&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; meters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;56.2 meters
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=b6fc3a98-5817-4a85-a376-713521bbe21d"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;Probabilistic answer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With probabilit we can compute braking distance as &lt;em&gt;a distribution&lt;/em&gt;.
More specifically we’ll be able to draw samples from the braking distance distribution.
All we have to do is allow some of the input parameters to be distributions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We do not trust the speedometer to be exact, so we set velocity to be a triangular distribution.&lt;/li&gt;
&lt;li&gt;We do not trust that the coefficient of kinetic friction is exactly 0.7, so we use a normal distribution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These distributions are somewhat arbitrary.
Any distribution in scipy can be used from within probabilit, with the syntax&lt;br/&gt;
&lt;code&gt;Distribution("scipy_distr_name", scipy_distr_arg1, scipy_distr_arg2, ...)&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=36af84a6-23bf-4ef5-bdf5-21230dbe3fe6"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [3]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;

&lt;span class="c1"&gt;# Replace velocity and coefficient of friction with distributions&lt;/span&gt;
&lt;span class="n"&gt;velocity_kmh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"triang"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&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="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mu_dry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"norm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# We can use the same function as earlier&lt;/span&gt;
&lt;span class="n"&gt;distance_dry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;braking_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity_kmh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu_dry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Sample from the answer. This samples the distributions above&lt;/span&gt;
&lt;span class="c1"&gt;# and propagates the samples through the expression defined by the&lt;/span&gt;
&lt;span class="c1"&gt;# 'braking_distance' function. "lhs" = latin hypercube sampling&lt;/span&gt;
&lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distance_dry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&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;bins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"auto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;density&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edgecolor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"black"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Braking distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=204471e2-0cce-4b3a-a755-e10be4244bc8"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [4]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;pd&lt;/span&gt;
&lt;span class="c1"&gt;# We can also get summary statistics&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Series&lt;/span&gt;&lt;span class="p"&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;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"braking_distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percentiles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_frame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&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="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[4]:&lt;/div&gt;
&lt;div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/html" tabindex="0"&gt;
&lt;div&gt;
&lt;style scoped=""&gt;
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
&lt;/style&gt;
&lt;table border="1" class="table-striped table-hover dataframe table"&gt;
&lt;thead&gt;
&lt;tr style="text-align: right;"&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;th&gt;mean&lt;/th&gt;
&lt;th&gt;std&lt;/th&gt;
&lt;th&gt;min&lt;/th&gt;
&lt;th&gt;1%&lt;/th&gt;
&lt;th&gt;5%&lt;/th&gt;
&lt;th&gt;50%&lt;/th&gt;
&lt;th&gt;95%&lt;/th&gt;
&lt;th&gt;99%&lt;/th&gt;
&lt;th&gt;max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;braking_distance&lt;/th&gt;
&lt;td&gt;2500.0&lt;/td&gt;
&lt;td&gt;56.3&lt;/td&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;td&gt;47.1&lt;/td&gt;
&lt;td&gt;49.3&lt;/td&gt;
&lt;td&gt;50.9&lt;/td&gt;
&lt;td&gt;56.2&lt;/td&gt;
&lt;td&gt;62.0&lt;/td&gt;
&lt;td&gt;64.3&lt;/td&gt;
&lt;td&gt;72.3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=f784ad1a-bf8c-44f2-b49b-cad8b37c5a81"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;With uncertainty added to some of the input parameters, the answer is not nearly as certain as it seemed in the deterministic analysis.
We have learned that braking distance could reasonably be anywhere between 51 meters and 62 meters or so.
This is the power of (simple) Monte Carlo analysis.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;distance_dry&lt;/code&gt; is sampled, the &lt;em&gt;ancestor nodes&lt;/em&gt; &lt;code&gt;velocity_kmh&lt;/code&gt; and &lt;code&gt;mu_dry&lt;/code&gt; are also sampled.
In the process, the attribute &lt;code&gt;samples_&lt;/code&gt; is set on &lt;em&gt;all&lt;/em&gt; nodes.
This is useful if we want to inspect the samples drawn on ancestor variables.
For instance, here is a simple sensitivity analysis where we study the effect of the input parameters on the output:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=9211ac7e-50d7-43a5-a59e-7419931a2710"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [5]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ax2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subplots&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sharey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;velocity_kmh&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;distance_dry&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;s&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="n"&gt;zorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mu_dry&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;distance_dry&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;s&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="n"&gt;zorder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Distance"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Velocity"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Coefficient of friction"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zorder&lt;/span&gt;&lt;span class="o"&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;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ax2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zorder&lt;/span&gt;&lt;span class="o"&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;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tight_layout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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vLw8lJSUYPbs2SYfIyAgAL1799Z7XuxJdZ4NlUFISAguXryo8X5zczMuX76st5xCQkLQ2NiIuro6venai5gYVX799VfcfffdSEpKwnvvvWfysRISEgDA7mVrSozqEhIS0NzcjLNnz+pN11HKUSxz6hW5XI7evXtrfKvv168fampq0NjYaPe6yhYx6GLrusiS83b9+nVs2rSpVUfO3M+6JWwRhy7G2kxLmRPHyy+/jMceewyzZ89GTEwMHnjgASxfvhwrVqyAUqm06rXhVB0mFW9vb8jlcly5cgV79+5FWlqazv2qqqpQW1sLuVyu832pVIr4+Hjs27dP2KZUKrFv3z4kJibaJO9iGYtx3bp1iI+Px8CBA01Ou6GhAeXl5XrPiz1FRkYiJCREowyuXbuGo0ePCmWQmJiIuro6jbts+/fvh1KpFDoI2uLj4+Hh4aGRbklJCSorK+1etmJiBO7cWRozZgzi4+ORnZ0NNzfTL9+ioiIAsHvZio1RW1FREdzc3Fr95KriSOUoljn1yogRI1BWVgalUilsO336NORyOaRSqd3rKlvEoIut6yJLztuWLVtw+/ZtPProoxrbzf2sW8IWcehirM20lDlx3Lhxo1VdqOqUE5F1rw2Thog7uC+++IL27NlDZ86codzcXBo4cCAlJCRQY2Mj1dfX03PPPUf5+flUUVFBX331FQ0ePJh69epFt27dEtK455576J133hFeb9q0iWQyGeXk5NCpU6do7ty5FBAQQDU1NW0RosEYVa5evUodO3akNWvW6ExDO8Znn32WDhw4QBUVFXTo0CFKSUmhLl260MWLF20eDxFRfX09ff/99/T9998TAFq9ejV9//339MsvvxDRnUd0AwICaOfOnfTjjz9SWlqazmkF4uLi6OjRo/Ttt99Sr169NKYVqKqqoj59+tDRo0eFbfPmzaMePXrQ/v376fjx45SYmEiJiYkOGWNVVRX17NmTxo4dS1VVVRqP+eqLsaysjF555RU6fvw4VVRU0M6dOykqKopGjx7tkDEePnyYMjMzqaioiMrLy+njjz+mrl270uOPP643RiL7lqO1GKtXHnvsMfrb3/4m7F9ZWUm+vr60YMECKikpoV27dlFQUBC9+uqrotNsDzG0RV1kahwqI0eOpClTpuhMU0yd5ehxiG0z2zoOhUJBvr6+tHHjRqFdjI6OpsmT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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=0413cc46-bf81-440e-92da-d827280fed9a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Mutual-fund"&gt;Mutual fund&lt;a class="anchor-link" href="#Mutual-fund"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=9a0f397f-7738-4a2d-aeb5-079ebcaf1dd8"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;.
Suppose we save 100 units of money per month for 10 years and put the money in a mutual fund.
The yearly interest rate is around 7 % after accounting for inflation.
How much money will we have after 10 years?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Probabilistic solution&lt;/strong&gt;.
The interest rate will not be exactly 7 % every year, so we can add some uncertainty.
Notice how probabilit expressions can be used in for-loops without any issues.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=1f985daf-61cd-4d9d-a7f0-00f9cc4a797d"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [6]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Lognormal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot&lt;/span&gt;

&lt;span class="n"&gt;YEARS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;SAVED_PER_MONTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="n"&gt;money_saved&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;year&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="n"&gt;YEARS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Lognormal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.07&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;money_saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;money_saved&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;interest_rate&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SAVED_PER_MONTH&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;

&lt;span class="c1"&gt;# Sample and plot the result&lt;/span&gt;
&lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;money_saved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pairgrid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;money_saved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# returns a seaborn pairgrid&lt;/span&gt;
&lt;span class="n"&gt;pairgrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axes&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;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Money saved"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pairgrid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;axes&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;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=6f2ee889-ba8a-46fb-befd-342413cb215a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;&lt;strong&gt;Exercise&lt;/strong&gt;.
Change the example above so that money saved per month is a distribution. Choose values that are realistic for your own financial situation and study how much money you can save over the next decade.&lt;/p&gt;
&lt;h2 id="Bird-survival"&gt;Bird survival&lt;a class="anchor-link" href="#Bird-survival"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;This final example illustrates &lt;a href="https://en.wikipedia.org/wiki/Compound_probability_distribution"&gt;compound distributions&lt;/a&gt;, where one distribution is an argument to another.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;.
Suppose we have a distribution of eggs per nest for a certain species of bird.
We also have a survival percentage for each egg.
What is the distribution of the total number of birds per nest that live till adulthood?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Probabilistic solution&lt;/strong&gt;. We set up separate distributions and pass them as arguments to each other:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=531fc369-1966-4e8d-9794-4a256cf2871f"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [7]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Number of eggs per nest&lt;/span&gt;
&lt;span class="n"&gt;eggs_per_nest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"poisson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mu&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="c1"&gt;# Assume we are uncertain of the survival probability too&lt;/span&gt;
&lt;span class="c1"&gt;# According to one source online, around 10-30% of eggs survive&lt;/span&gt;
&lt;span class="n"&gt;prob_survive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"beta"&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Here is the compound distribution, which depends on two distributions&lt;/span&gt;
&lt;span class="n"&gt;adult_birds_per_nest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"binom"&lt;/span&gt;&lt;span class="p"&gt;,&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;eggs_per_nest&lt;/span&gt;&lt;span class="p"&gt;,&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;prob_survive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Calling plot() without sampling will sample a copy of the nodes&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;eggs_per_nest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;prob_survive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;adult_birds_per_nest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sample_kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"random_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=f465fad3-3224-426c-b9a8-e9c12283300a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;The discrete nature of the distributions make them hard to visualize without jitter in the default &lt;code&gt;plot&lt;/code&gt; call, but hopefully you get the idea.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exercise&lt;/strong&gt;.
Go to Wikipedia’s &lt;a href="https://en.wikipedia.org/wiki/Compound_probability_distribution#Examples"&gt;examples of compound distributions&lt;/a&gt;.
Verify computationally that this statement is true: “Compounding a Gaussian distribution with variance distributed according to an exponential distribution yields a Laplace distribution.”&lt;/p&gt;
&lt;h1 id="How-does-probabilit-work?"&gt;How does probabilit work?&lt;a class="anchor-link" href="#How-does-probabilit-work?"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Given an expression, probabilit creates a computational graph that is evaluated lazily.
For instance, the expression &lt;code&gt;Constant(5) ** 2&lt;/code&gt; is converted to &lt;code&gt;Power(Constant(5), Constant(2))&lt;/code&gt;, where &lt;code&gt;Power&lt;/code&gt; is a &lt;em&gt;Transform&lt;/em&gt; node.
Other transform nodes are &lt;code&gt;Exp&lt;/code&gt;, &lt;code&gt;Log&lt;/code&gt;, &lt;code&gt;Sin&lt;/code&gt;, &lt;code&gt;Abs&lt;/code&gt;, &lt;code&gt;Add&lt;/code&gt;, &lt;code&gt;Divide&lt;/code&gt;, etc.
There are also &lt;em&gt;Constant&lt;/em&gt; nodes and &lt;em&gt;Distribution&lt;/em&gt; nodes.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=3e837024-e78f-4db5-bb3b-abfa7dd5e24e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [8]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Constant&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Constant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"uniform"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;Constant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;Add(Power(Constant(5), Constant(2)), Divide(Distribution("uniform"), Constant(3)))
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=69051b69-f641-4c25-ad7d-f47367db7f3c"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;The computational graph can be visualized as a tree:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=fe6cdb24-432d-4ff1-876c-3589bbc36b58"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [9]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;treeprint&lt;/span&gt;
&lt;span class="n"&gt;treeprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;Add
   ├──Power
   │  ├──Constant(5)
   │  └──Constant(2)
   └──Divide
      ├──Distribution("uniform")
      └──Constant(3)
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=94a3dc73-5ac3-4756-94e8-c27e5b3c18b1"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Not every probabilit expression is a tree.
For instance, if &lt;code&gt;a&lt;/code&gt; is a node then &lt;code&gt;b = abs(a) + a&lt;/code&gt; creates a graph that is not strictly speaking a tree.
All probabilit graphs are DAGs (directed acyclic graphs), since they cannot contain cycles.&lt;/p&gt;
&lt;p&gt;A probabilit expression can be converted to a networkx &lt;a href="https://networkx.org/documentation/stable/reference/classes/multidigraph.html"&gt;MultiDiGraph&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=979f7eaf-41fd-4a44-bdb7-9be99885ef12"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [10]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;networkx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;nx&lt;/span&gt;

&lt;span class="c1"&gt;# Create a MultiDiGraph&lt;/span&gt;
&lt;span class="n"&gt;G&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_graph&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Draw information&lt;/span&gt;
&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spring_layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&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;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;

&lt;span class="c1"&gt;# Draw the graph&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;with_labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;node_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lightblue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;node_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;font_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;font_weight&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bold"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arrows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;arrowsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;edge_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=08b6a93e-aca8-47ab-8f48-a1ad45a0e50a"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;It’s important to understand that no computation is performed until &lt;code&gt;.sample()&lt;/code&gt; is called on a node.
The expression above is just a graph with instructions about &lt;em&gt;how to compute a result&lt;/em&gt;. Once &lt;code&gt;.sample()&lt;/code&gt; is called, the actual work is done:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=79dfece3-5127-4059-b20c-1107c56960f5"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [11]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[11]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([25.12484671, 25.31690477, 25.24399798, 25.19955283, 25.05200621,
       25.05199817, 25.0193612 , 25.28872538, 25.20037167, 25.23602419])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=c5b03cd0-d2b9-4ad9-9c05-349a56157fe8"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;When the call asks for the sink node &lt;code&gt;expression&lt;/code&gt; to be sampled, all ancestors are sampled in turn and results are propagated through the graph.
This has both memory and performance overhead, which probabilit sacrifices in favor of a clean &lt;span class="caps"&gt;API&lt;/span&gt; and a pleasant modeling experience.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=51879981-dc06-4680-b36a-5aa621168763"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h1 id="Latin-HyperCube-sampling-(LHS)"&gt;Latin HyperCube sampling (&lt;span class="caps"&gt;LHS&lt;/span&gt;)&lt;a class="anchor-link" href="#Latin-HyperCube-sampling-(LHS)"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Probabilit supports all major sampling methods in the scipy &lt;a href="https://docs.scipy.org/doc/scipy/reference/stats.qmc.html"&gt;quasi-monte carlo module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Below we show the difference between pseudo-random sampling (&lt;code&gt;method=None&lt;/code&gt;) and Latin HyperCube (&lt;code&gt;method="lhs"&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=b6850f9b-2902-48f1-bd3d-381ec12101e5"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [12]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"uniform"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;exponential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"expon"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Sample a sum to set `.samples_` on distributions for plotting&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=3c1aa45e-f8f3-4b4d-87f9-b040a0bd1e4f"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [13]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=93aa3147-ba05-4381-9eba-57c2f49e61f6"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Notice how the &lt;a href="https://en.wikipedia.org/wiki/Low-discrepancy_sequence"&gt;low-discrepancy sequence&lt;/a&gt; produced by &lt;span class="caps"&gt;LHS&lt;/span&gt; leads to distributions of samples that looks very much like the theoretical distributions.
In many Monte Carlo simulations, using &lt;span class="caps"&gt;LHS&lt;/span&gt; is an excellent choice because we obtain empirical distributions that match the theoretical ones with fewer samples drawn than what we would achieve had we used pseudo-random sequences.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=913c72b3-0c86-42ed-9219-ccc11d64745e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h1 id="Inducing-correlations"&gt;Inducing correlations&lt;a class="anchor-link" href="#Inducing-correlations"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Probabilit comes with &lt;code&gt;Correlator&lt;/code&gt; objects that can be used to correlate variables.
They work within the framework of the modeling &lt;span class="caps"&gt;API&lt;/span&gt;, or outside of it (where numpy arrays can be used).
This means that these highly performant algorithms can be used outside of the modeling language too.&lt;/p&gt;
&lt;p&gt;When all variables are Normal the Cholesky correlator can be a good choice.
While it obtains the desired correlation, it does not preserve the marginal distributions.
This is a major problem many real-world applications, so &lt;strong&gt;be careful when using the Cholesky correlator!&lt;/strong&gt;
Notice in the example below that the exponential distribution contains negative values after inducing correlations.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=128fa4bd-a879-46e5-b354-34e025d4b947"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [14]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Cholesky&lt;/span&gt;

&lt;span class="c1"&gt;# Correlation matrix with desired Pearson correlation of 0.8&lt;/span&gt;
&lt;span class="n"&gt;corr_mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Cholesky&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Observed Pearson correlation&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&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;exponential&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;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[14]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([[1. , 0.8],
       [0.8, 1. ]])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=b620f9cb-eec8-47e9-a37c-f2705e464ff6"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Above, the marginal of the uniform distribution was preserved.
This is a happy accident since we defined that distribution &lt;em&gt;before&lt;/em&gt; we defined the exponential distribution in the code.
If we switch them, then we will preserve the marginals of the exponential but destroy the marginal of the uniform.
In general the Cholesky comes with no guarantees of preserving marginals.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exercise&lt;/strong&gt;.
Switch the order and define the exponential distribution first, then the uniform distribution.
Use the Cholesky correlator and notice how the marginal of the uniform is destroyed.&lt;/p&gt;
&lt;h2 id="Iman-Conover"&gt;Iman-Conover&lt;a class="anchor-link" href="#Iman-Conover"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The Iman-Conover transformation is almost always a better choice than Cholesky.
It leaves the marginal distributions unchanged, while attempting to induce the desired correlation.
Iman-Conover is essentially a heuristic where data is transformed to rank-space, then Cholesky is used, then the data is transformed back.
The end result is a permutation of the samples in each distribution.
The idea is that by choosing a permutation that induces correlation in rank space, the permutation will also get close to the desired correlation in the original space.
This typically works pretty well in most cases, but the algorithm comes without any optimality guarantees.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=3d91c9bf-b73e-4bb6-994a-385464cb9b26"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [15]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ImanConover&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ImanConover&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&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;exponential&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;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[15]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([[1.        , 0.70443783],
       [0.70443783, 1.        ]])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=bfb11846-ec78-448f-859c-54fe55a5c335"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Above we do not achieve the exact correlation that we wanted, but we get pretty close.
The marginals are unaffected by the transformation.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=5021a904-4c86-4347-a4f9-4337bccb2818"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h2 id="Inducing-correlations-by-permutation"&gt;Inducing correlations by permutation&lt;a class="anchor-link" href="#Inducing-correlations-by-permutation"&gt;¶&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Since Iman-Conover is just a clever and fast heuristic to pick a good permutation of the samples (rows) within a variable (column) in a matrix, perhaps we can do even better if we search the space of all permutations?
This space is huge, so we can only afford to search a small part of it. The &lt;code&gt;Permutation&lt;/code&gt; correlation does this, and by spending some time on computation we often end up with even better correlations than what Iman-Conover achieves.
Notice below that we get extremely close to the desired correlation.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=a3070d93-647e-4270-b52e-65240fb8276e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [16]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Permutation&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;correlator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Permutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plot_kws&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniform&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;exponential&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;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[16]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([[1.        , 0.78002787],
       [0.78002787, 1.        ]])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedImage jp-OutputArea-output" tabindex="0"&gt;
&lt;img alt="No description has been provided for this image" class="img-fluid rounded" src="data:image/png;base64,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"/&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=6d401403-4303-4766-80b2-03fc355e9d3e"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;Finally, let us compare &lt;code&gt;ImanConover&lt;/code&gt; with the &lt;code&gt;Composite&lt;/code&gt; algorithm.
The composite approach is what probabilit does by default.
It first uses Iman-Conover to find a good starting point, then proceeds by running Permutation on that starting point for a number of iterations.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=dcd5af9e-542f-4547-97f3-4fc257b9ae61"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [17]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"lognorm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&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;for&lt;/span&gt; &lt;span class="n"&gt;_&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;10&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="c1"&gt;# Set up correlation matrix&lt;/span&gt;
&lt;span class="n"&gt;corr_mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;correlate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;corr_mat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Iman-Conover&lt;/span&gt;
&lt;span class="n"&gt;correlator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImanConover&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;observed_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&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;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;RMSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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;np&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="n"&gt;corr_mat&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;observed_corr&lt;/span&gt;&lt;span class="p"&gt;)&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"RMSE (observed-desired) for Iman-Conover: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RMSE&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.4f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;RMSE (observed-desired) for Iman-Conover: 0.1011
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=49fc29c8-43d7-4b10-81eb-eacc14a0df34"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [18]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Composite&lt;/span&gt;

&lt;span class="c1"&gt;# Composite() =&amp;gt; ImanConover(), then Permutation()&lt;/span&gt;
&lt;span class="n"&gt;correlator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Composite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&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;iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"lhs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;correlator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;observed_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;corrcoef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&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;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples_&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;RMSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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;np&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="n"&gt;corr_mat&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;observed_corr&lt;/span&gt;&lt;span class="p"&gt;)&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"RMSE norm (observed-desired) for Composite: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;RMSE&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.4f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;RMSE norm (observed-desired) for Composite: 0.0367
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=9226feb3-f3e4-4a91-b168-274a34f17027"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;p&gt;If we are willing to spend some additional time on computation (here 2.5 seconds instead of 15 milliseconds), then we can obtain correlations that are much closer to what we desired.
Often this is worth it, as speed is rarely a bottleneck in these types of analyses.&lt;/p&gt;
&lt;h1 id="Nearest-correlation-matrix"&gt;Nearest correlation matrix&lt;a class="anchor-link" href="#Nearest-correlation-matrix"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;Just like the &lt;code&gt;ImanConover&lt;/code&gt; and &lt;code&gt;Permutation&lt;/code&gt; correlator classes can be used without using the modeling &lt;span class="caps"&gt;API&lt;/span&gt;, the &lt;code&gt;nearest_correlation_matrix&lt;/code&gt; can also be used without using the modeling &lt;span class="caps"&gt;API&lt;/span&gt;.
This algorithm is useful when users (e.g. a domain expert) proposes correlations, but they end up proposing a correlation matrix that is not valid.
This happens quite frequently, since specifying a high-dimensional, non-trivial correlation matrix that is positive definite is hard.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=75820de5-3d70-4117-861b-ebd3f5ceb192"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [19]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;probabilit.correlation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nearest_correlation_matrix&lt;/span&gt;

&lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&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;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;proposed_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&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;high&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;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Make it symmetric&lt;/span&gt;
&lt;span class="n"&gt;proposed_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;triu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;triu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&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;T&lt;/span&gt;
&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&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;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[19]:&lt;/div&gt;
&lt;div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/html" tabindex="0"&gt;
&lt;div&gt;
&lt;style scoped=""&gt;
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
&lt;/style&gt;
&lt;table border="1" class="table-striped table-hover dataframe table"&gt;
&lt;thead&gt;
&lt;tr style="text-align: right;"&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-46.0&lt;/td&gt;
&lt;td&gt;-91.8&lt;/td&gt;
&lt;td&gt;-96.7&lt;/td&gt;
&lt;td&gt;62.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;td&gt;-46.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;45.9&lt;/td&gt;
&lt;td&gt;8.7&lt;/td&gt;
&lt;td&gt;87.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;td&gt;-91.8&lt;/td&gt;
&lt;td&gt;45.9&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-93.3&lt;/td&gt;
&lt;td&gt;45.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;td&gt;-96.7&lt;/td&gt;
&lt;td&gt;8.7&lt;/td&gt;
&lt;td&gt;-93.3&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-15.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;td&gt;62.7&lt;/td&gt;
&lt;td&gt;87.0&lt;/td&gt;
&lt;td&gt;45.9&lt;/td&gt;
&lt;td&gt;-15.5&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=5b238a28-18ad-4f8a-8070-b342d2a5c333"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [20]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eigvals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Negative eigenvalues =&amp;gt; not positive definite&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[20]:&lt;/div&gt;
&lt;div class="jp-RenderedText jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/plain" tabindex="0"&gt;
&lt;pre&gt;array([-1.05418258, -0.00675076,  1.41936444,  2.20321725,  2.43835165])&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="jp-Cell jp-CodeCell jp-Notebook-cell" id="cell-id=397a687a-8991-4c27-b958-8642a4685be5"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;
&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;In [21]:&lt;/div&gt;
&lt;div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor" data-type="inline"&gt;
&lt;div class="cm-editor cm-s-jupyter"&gt;
&lt;div class="highlight hl-ipython3"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;nearest_corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nearest_correlation_matrix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nearest_corr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&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;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell-outputWrapper"&gt;
&lt;div class="jp-Collapser jp-OutputCollapser jp-Cell-outputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-OutputArea jp-Cell-outputArea"&gt;
&lt;div class="jp-OutputArea-child jp-OutputArea-executeResult"&gt;
&lt;div class="jp-OutputPrompt jp-OutputArea-prompt"&gt;Out[21]:&lt;/div&gt;
&lt;div class="jp-RenderedHTMLCommon jp-RenderedHTML jp-OutputArea-output jp-OutputArea-executeResult" data-mime-type="text/html" tabindex="0"&gt;
&lt;div&gt;
&lt;style scoped=""&gt;
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
&lt;/style&gt;
&lt;table border="1" class="table-striped table-hover dataframe table"&gt;
&lt;thead&gt;
&lt;tr style="text-align: right;"&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-35.0&lt;/td&gt;
&lt;td&gt;-42.5&lt;/td&gt;
&lt;td&gt;-51.7&lt;/td&gt;
&lt;td&gt;35.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;td&gt;-35.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;49.9&lt;/td&gt;
&lt;td&gt;11.4&lt;/td&gt;
&lt;td&gt;75.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;td&gt;-42.5&lt;/td&gt;
&lt;td&gt;49.9&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-51.0&lt;/td&gt;
&lt;td&gt;26.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;td&gt;-51.7&lt;/td&gt;
&lt;td&gt;11.4&lt;/td&gt;
&lt;td&gt;-51.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;-32.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;td&gt;35.2&lt;/td&gt;
&lt;td&gt;75.0&lt;/td&gt;
&lt;td&gt;26.8&lt;/td&gt;
&lt;td&gt;-32.1&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="jp-Cell jp-MarkdownCell jp-Notebook-cell" id="cell-id=8d308d4b-df73-492d-9e66-5201497c449d"&gt;
&lt;div class="jp-Cell-inputWrapper" tabindex="0"&gt;
&lt;div class="jp-Collapser jp-InputCollapser jp-Cell-inputCollapser"&gt;
&lt;/div&gt;
&lt;div class="jp-InputArea jp-Cell-inputArea"&gt;&lt;div class="jp-InputPrompt jp-InputArea-prompt"&gt;
&lt;/div&gt;&lt;div class="jp-RenderedHTMLCommon jp-RenderedMarkdown jp-MarkdownOutput" data-mime-type="text/markdown"&gt;
&lt;h1 id="Summary-and-references"&gt;Summary and references&lt;a class="anchor-link" href="#Summary-and-references"&gt;¶&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;&lt;a href="https://github.com/equinor/probabilit"&gt;probabilit&lt;/a&gt; has two layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A high-level modeling language, built on top of Python&lt;/li&gt;
&lt;li&gt;Low-level functionality for correlating variables, finding nearest correlation matrices, etc&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Depending on what kind of Monte Carlo analysis you want to do, one or both (or none!) of these might be useful.&lt;/p&gt;
&lt;p&gt;Probabilit is a bit like Tensorflow in that it uses a lazily evaluated computational graph.
The idea of building a Monte Carlo analysis tool in this fashion is due to &lt;a href="https://github.com/dafeda/"&gt;Feda&lt;/a&gt;.
Integrating &lt;span class="caps"&gt;LHS&lt;/span&gt; and ImanConover into the tool was also his idea.
&lt;a href="https://github.com/knutuh/"&gt;Knut&lt;/a&gt; provided feedback on practical methods and distributions used in some types of Monte Carlo analysis, e.g. the importance of Triangular and &lt;span class="caps"&gt;PERT&lt;/span&gt; distributions.
The idea behind the Permutation correlation and how to speed it up by not re-calculating the correlation matrix in every iteration (an idea we did not have time to go into in this tutorial) is my own, but these are not novel ideas.&lt;/p&gt;
&lt;p&gt;To read more on these subjects, I recommend:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/equinor/probabilit"&gt;probabilit&lt;/a&gt; repository. Have a look at the implementations. Create an issue or &lt;span class="caps"&gt;PR&lt;/span&gt; if you find a bug :)&lt;/li&gt;
&lt;li&gt;To find the nearest correlation matrix, we use Equation (3) in the paper &lt;a href="https://ieeexplore.ieee.org/document/8160870"&gt;An augmented Lagrangian dual approach for the H-weighted nearest correlation matrix problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An excellent explanation of the Iman Conover algorithm is found in &lt;a href="https://blogs.sas.com/content/iml/2021/06/16/geometry-iman-conover-transformation.html"&gt;The geometry of the Iman-Conover transformation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The book &lt;a href="https://onlinelibrary.wiley.com/doi/book/10.1002/9780470725184"&gt;Global Sensitivity Analysis&lt;/a&gt; by Saltelli et al. is not directly related to Monte Carlo modeling, but Monte Carlo is often used for sensitivity analysis of models, and therefore this book might be of interest&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/main&gt;
</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 17 Oct 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-10-17:/articles/2025/monte-carlo-modeling-in-python-with-probabilit</guid><category>articles</category><category>statistics</category></item><item><title>A Recipe for Curve Fitting</title><link>https://tommyodland.com/articles/2025/a-recipe-for-curve-fitting</link><description>&lt;p&gt;Inspired by &lt;a href="http://karpathy.github.io/2019/04/25/recipe/"&gt;A Recipe for Training Neural Networks&lt;/a&gt;, here are some tips and tricks for curve fitting.
Assume we are fitting a non-linear, parametric function &lt;span class="math"&gt;\(f(x; \boldsymbol{\theta})\)&lt;/span&gt; to data &lt;span class="math"&gt;\(y\)&lt;/span&gt;.
The problem could be anything.
Many highly technical problems are illuminated by curve fitting, but it helps to think of everyday examples such as: personal savings vs. time, apartment price vs. square meters, salary vs. experience, strength vs. bodyweight, world population vs. year, etc.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/curve_fitting/curve_fitting_animation.gif" style="display: block; margin-left: auto; margin-right: auto; max-height: 260px;"/&gt;&lt;/p&gt;
&lt;h2 id="modeling"&gt;Modeling&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Determine the purpose.&lt;/strong&gt; Do you want to extrapolate, or do you want to interpolate? Is &lt;em&gt;prediction&lt;/em&gt; the overall goal, or is it &lt;em&gt;inference&lt;/em&gt; of latent variables? In other words: raw predictive power or understanding underlying phenomena? Is uncertainty needed? If yes, then what kind? A posterior conditional distribution &lt;span class="math"&gt;\(p(x | \hat{\boldsymbol{\theta}})\)&lt;/span&gt; or a posterior predictive distribution &lt;span class="math"&gt;\(p(x | \boldsymbol{\theta}) p(\boldsymbol{\theta} | X)\)&lt;/span&gt; that accounts for parameter uncertainty? Is curve fitting a one-time job, or will it every night or on demand? Will it run on a single data set, or perhaps on thousands of datasets?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read the literature.&lt;/strong&gt; Before deciding on the function &lt;span class="math"&gt;\(f\)&lt;/span&gt;, search the web and review papers. What functions are used on similar problems? Avoid ad-hoc models like unregularized high-degree polynomials or very intricate functions. Apply &lt;a href="https://en.wikipedia.org/wiki/Occam%27s_razor"&gt;Occam’s razor&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Know basic functions and how to combine them.&lt;/strong&gt; Know the behavior of &lt;a href="https://en.wikipedia.org/wiki/List_of_mathematical_functions"&gt;basic building blocks&lt;/a&gt; like linear functions, powers, exponentials, logarithms, sigmoids, hyperbolic functions, etc. Sometimes you can transition between them in more natural ways than the convex combination &lt;span class="math"&gt;\(\alpha f(x) + (1- \alpha) g(x)\)&lt;/span&gt;. For instance, the &lt;a href="https://en.wikipedia.org/wiki/Power_transform"&gt;power transform&lt;/a&gt; &lt;span class="math"&gt;\((x^\alpha - 1) / \alpha\)&lt;/span&gt; lies between the affine function &lt;span class="math"&gt;\(x-1\)&lt;/span&gt; when &lt;span class="math"&gt;\(\alpha=1\)&lt;/span&gt; and the logarithmic function &lt;span class="math"&gt;\(\ln x\)&lt;/span&gt; when &lt;span class="math"&gt;\(\alpha \to 0\)&lt;/span&gt;. Similarly, the &lt;a href="https://en.wikipedia.org/wiki/Generalized_Pareto_distribution"&gt;generalized pareto&lt;/a&gt; distribution lies “between” the exponential &lt;span class="math"&gt;\(e^{-x}\)&lt;/span&gt; and the hyperbola &lt;span class="math"&gt;\((1 + x)^{-1}\)&lt;/span&gt;. Probability density functions and their antiderivatives are a rich source of modeling functions. Know how to combine functions to create new models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use an identifiable model.&lt;/strong&gt; Avoid using non-identifiable models like &lt;span class="math"&gt;\(f(x; \boldsymbol{\theta}) = \theta_1 \exp(x - \theta_2)\)&lt;/span&gt;, since they do not have a unique solution. This seems obvious, but I’ve seen papers where non-identifiable models are used without priors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Choose an appropriate loss.&lt;/strong&gt; The least squares loss &lt;span class="math"&gt;\(\mathcal{L}(\boldsymbol{\theta}) = \sum_i (y_i - f(x_i; \boldsymbol{\theta}))^2\)&lt;/span&gt; has nice properties, but might &lt;a href="https://tommyodland.com/articles/2024/the-average-value"&gt;not be what you want&lt;/a&gt;. Know when to use &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norms, huber loss, &lt;a href="https://tommyodland.com/articles/2024/the-reverse-pseudo-huber-loss-function"&gt;pseudohuber&lt;/a&gt;, geometric loss, harmonic loss and statistical losses. Remember that fitting log-data to log-model with a loss like &lt;span class="math"&gt;\((\log y_i - \log f(x_i; \boldsymbol{\theta}))^2\)&lt;/span&gt; is not equivalent to fitting data to a model without logs. Determining the appropriate loss function is a big part of the job. Always align the primary metric with the loss. If you care about &lt;span class="caps"&gt;MAE&lt;/span&gt; as the metric, it makes little sense to use &lt;span class="caps"&gt;RMSE&lt;/span&gt; as the loss.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use a prior&lt;/strong&gt;, or if the model is not statistical, a regularization term. A prior encodes prior knowledge and helps the solver numerically. It also lets you get a solution when you have little data or no data at all. The 2-norm &lt;span class="math"&gt;\(\lVert \boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}} \rVert_2\)&lt;/span&gt; is a good start and &lt;span class="math"&gt;\((\boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}})^T A (\boldsymbol{\theta} - \boldsymbol{\mu}_{\boldsymbol{\theta}})\)&lt;/span&gt; can be even better. Often &lt;span class="math"&gt;\(A\)&lt;/span&gt; is diagonal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deal with parameter constraints.&lt;/strong&gt; There are several options: (1) use an optimizer that handles constraints natively, (2) re-parametrize the function as &lt;span class="math"&gt;\(f(x; t(\boldsymbol{\theta}))\)&lt;/span&gt; with &lt;span class="math"&gt;\(t\)&lt;/span&gt; mapping from unbounded real numbers to the desired bounds or (3) add a &lt;a href="https://en.wikipedia.org/wiki/Barrier_function"&gt;barrier function&lt;/a&gt; to the objective. For instance, if &lt;span class="math"&gt;\(\theta_1 \in [0, 1]\)&lt;/span&gt;, then you can (1) use an algorithm like &lt;span class="caps"&gt;BFGS&lt;/span&gt;-B (the B stands for “box constraints”), (2) transform with e.g. a sigmoid like &lt;span class="math"&gt;\(\theta_1 = t(z) = (1 + e^{-z})^{-1}\)&lt;/span&gt; or (3) add barriers such as &lt;span class="math"&gt;\(-\log(\theta_1) - \log (1 - \theta_1 )\)&lt;/span&gt; to the objective. The log-barrier is equivalent to a beta-prior.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weight the data.&lt;/strong&gt; Consider a weighted loss like &lt;span class="math"&gt;\(\sum_i w_i (y_i - f(x_i; \boldsymbol{\theta}))^2\)&lt;/span&gt; if (1) some data points are more important than others or (2) you are working on a temporal forecasting problem. Beware of the difference between (a) &lt;a href="https://en.wikipedia.org/wiki/Inverse-variance_weighting"&gt;inverse variance weights&lt;/a&gt; and (b) weighting the log-likelihood. They are the same when estimating the mean of a normal, but &lt;a href="https://notstatschat.rbind.io/2020/08/04/weights-in-statistics/"&gt;not in general&lt;/a&gt; (see also &lt;a href="https://blogs.sas.com/content/iml/2017/10/02/weight-variables-in-statistics-sas.html"&gt;this post&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consider partial pooling.&lt;/strong&gt; Use &lt;a href="https://en.wikipedia.org/wiki/Bayesian_hierarchical_modeling"&gt;hierarchical models&lt;/a&gt; to automatically regularize with hyperpriors. This is relevant if you have many curve fitting instances that you can conceptualize as being drawn from some population, and you want to learn the distribution of that population. It’s also extremely useful if some instances have little data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="validation"&gt;Validation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cross validate.&lt;/strong&gt; Split randomly if you’re interpolating. Use a time based split if you’re forecasting. Always &lt;a href="https://tommyodland.com/articles/2024/an-opinionated-guide-to-scikit-learn"&gt;match cross validation&lt;/a&gt; to how the model will be used. Compute metrics and study them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use your eyes.&lt;/strong&gt; Curve fitting can be visualized! Sample prior curves and plot them, plot the initial guess, curve fit “by hand” a few times, fit on every instance you have in your database. Visually look for errors, edge cases, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement the forward model.&lt;/strong&gt; Implement data generation based on your model if it’s statistical. This is called a prior predictive check. Can you tell a simulation apart from real-world observed data? How? If you can, does it give you ideas for how to improve the model?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test parameter estimation.&lt;/strong&gt; Some property-based tests: (1) generate a random &lt;span class="math"&gt;\(\boldsymbol{\theta}\)&lt;/span&gt;, use it to generate a dataset, fit on the dataset. Is &lt;span class="math"&gt;\(\lVert \boldsymbol{\theta} - \hat{\boldsymbol{\theta}} \rVert\)&lt;/span&gt; small? It should be, especially in the limit of much data. (2) Test that &lt;span class="math"&gt;\(\mathcal{L}(\hat{\boldsymbol{\theta}}) \leq \mathcal{L}(\boldsymbol{\theta})\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plot parameter-space.&lt;/strong&gt; You can plot the contours of the loss as a function of two parameters in &lt;span class="math"&gt;\(\boldsymbol{\theta}\)&lt;/span&gt; at a time, while fixing the remaining parameters at &lt;span class="math"&gt;\(\hat{\boldsymbol{\theta}}\)&lt;/span&gt;. Sometimes studying the Hessian is worthwhile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plot residuals.&lt;/strong&gt; When are residuals large? Are residuals &lt;a href="https://en.wikipedia.org/wiki/Homoscedasticity_and_heteroscedasticity"&gt;homoscedastic&lt;/a&gt;? Should you model heteroscedasticity?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="optimization"&gt;Optimization&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Understand the optimization algorithm.&lt;/strong&gt; Have at least a rudimentary understanding of the algorithms you use. Read the documentation and perhaps a summary paper. Play with the optimizer on toy problems. Get a feeling for how it works, and more importantly how and when it fails. Study the full verbose output of the algorithm.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Put variables on the same scale.&lt;/strong&gt; First order methods (gradient methods) depend critically on the condition number. Pure second order methods (Newton’s method) on the other hand are affine invariant and in principle unaffected by the condition number (numerics and quasi-Newton methods make this untrue in reality). Standardize or take logs/exp/sigmoid/etc as needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;De-correlate variables.&lt;/strong&gt; Sometimes you can de-correlate variables through re-parametrization of the curve function.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use a prior&lt;/strong&gt;. Even if you lack prior knowledge in the modeling sense, weak regularization tends to help the optimizer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pick a good starting point.&lt;/strong&gt; Graphical trial and error often works. You can also solve a simpler problem, e.g. solve a least squares problem first and use that solution as the starting point for your &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; norm problem. The curve function &lt;span class="math"&gt;\(f\)&lt;/span&gt; can sometimes be relaxed by removing non-linearities. You could use &lt;code&gt;(y[-1] - y[0])/(x[-1] - x[0])&lt;/code&gt; as initial guess for slope, &lt;code&gt;mean(y)&lt;/code&gt; as initial guess for bias, etc. Stress test the solver on bad initial solutions too.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement gradients and/or Hessian.&lt;/strong&gt; Take the time to work out gradients. Use a &lt;span class="caps"&gt;CAS&lt;/span&gt; like &lt;a href="https://www.sympy.org/en/index.html"&gt;SymPy&lt;/a&gt; or &lt;a href="https://www.wolframalpha.com/"&gt;WolframAlpha&lt;/a&gt; along with pen and paper. Verify gradients numerically with &lt;a href="https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.check_grad.html"&gt;scipy.optimize.check_grad&lt;/a&gt; or similar functions. If the gradient is too unwieldy, consider autograd with one of the many available packages: &lt;a href="https://github.com/jax-ml/jax"&gt;jax&lt;/a&gt;, &lt;a href="https://github.com/hips/autograd"&gt;autograd&lt;/a&gt;, &lt;a href="https://pytorch.org/"&gt;PyTorch&lt;/a&gt;, &lt;a href="https://github.com/tensorflow/tensorflow"&gt;TensorFlow&lt;/a&gt; or &lt;a href="https://mc-stan.org/"&gt;Stan&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use numerically stable implementations.&lt;/strong&gt; Watch out for expressions like &lt;span class="math"&gt;\(\ln(1 + e^{\theta})\)&lt;/span&gt; that overflow, &lt;a href="https://en.wikipedia.org/wiki/Catastrophic_cancellation"&gt;catastrophic cancellation&lt;/a&gt; and other phenomena. See &lt;a href="https://www.plunk.org/~hatch/rightway.html"&gt;The Right Way to Calculate Stuff&lt;/a&gt; for more examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Curve fitting seems easy, but this perception can be deceptive.
It’s not uncommon that people fail to get the desired results, often because they call standard routines without knowing how they work.
I hope the tips and tricks above can help.
Experience helps too: work on diverse practical problems and try to solve them.
Make notes on what works in practice.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sat, 04 Oct 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-10-04:/articles/2025/a-recipe-for-curve-fitting</guid><category>articles</category><category>mathematics</category></item><item><title>Lønna til norske utviklere i 2025</title><link>https://tommyodland.com/articles/2025/lonna-til-norske-utviklere-i-2025</link><description>&lt;p&gt;I denne artikkelen skal vi gå gjennom betraktninger og metoder for å lage en lønnskalkulator for &lt;span class="caps"&gt;IT&lt;/span&gt;-utviklere.
Utålmodige lesere kan gå rett til &lt;strong&gt;&lt;a href="https://tommyodland.com/tools/salary.html"&gt;Lønnskalkulatoren&lt;/a&gt;&lt;/strong&gt; og se hovedfunnene i figuren nedenfor.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_effects.png"&gt;
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_effects.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 780px;"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Nettsiden &lt;a href="https://www.kode24.no/"&gt;kode24&lt;/a&gt; har lenge hatt årlige spørreundersøkelser om lønn og publisert data i etterkant.
Vi kan finne datasett for &lt;a href="https://www.kode24.no/artikkel/vi-deler-ut-lonna-til-1542-utviklere-lag-noe-goy/136731"&gt;2021&lt;/a&gt;, &lt;a href="https://www.kode24.no/artikkel/vaer-sa-god-her-er-lonningene-til-over-1200-norske-utviklere/153993"&gt;2022&lt;/a&gt;, &lt;a href="https://www.kode24.no/artikkel/dykk-ned-i-kode24s-lonnstall-vis-oss-hva-du-lager/174298"&gt;2023&lt;/a&gt;, &lt;a href="https://www.kode24.no/artikkel/her-er-lonnstallene-for-norske-utviklere-2024/203888"&gt;2024&lt;/a&gt; og &lt;a href="https://www.kode24.no/artikkel/last-ned-kode24s-lonnstall-for-2025-og-lag-noe-sjaelv/244319"&gt;2025&lt;/a&gt; på nettsidene.&lt;/p&gt;
&lt;p&gt;Jeg har tidligere gjort flere analyser av spørreundersøkelsene:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I 2021 gjorde jeg en analyse i det &lt;a href="https://tommyodland.com/articles/2021/lonn-og-kjonn-blant-utviklere"&gt;probabilistiske programmeringsspråket Stan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I 2023 brukte jeg &lt;a href="https://tommyodland.com/articles/2023/lonna-til-norske-utviklere-i-2023"&gt;generaliserte additive modeller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I 2024 &lt;a href="https://tommyodland.com/articles/2024/lonna-til-norske-utviklere-i-2024"&gt;gjentok jeg analysen fra 2023 med det nye datasettet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Basert på 2024-tallene hjalp jeg også til med å lage kodejobb.no’s &lt;a href="https://kodejobb.no/lonn"&gt;Lønnskalkulator 2024&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Det meste som kan sies har blitt sagt: konsulenter tjener noe mer enn fast ansatte, bosatte i Oslo får en lønnspremie, ledere og arkitekter tjener mest, kjønn er (heldigvis) ubetydelig, og antall år med erfaring dominerer alle andre variabler.
Størsteparten av variasjonen i lønn skyldes variabler som spørreundersøkelsene ikke plukker opp.
Til sammen forklarer variablene i lønnsundersøkelsene mellom 30% og 50% av variasjonen i lønna; hvor mye avhenger av datasettet og modellen.
Les mine tidligere analyser for en nøye gjennomgang.&lt;/p&gt;
&lt;p&gt;Årets pro-bono bidrag til &lt;span class="caps"&gt;IT&lt;/span&gt;-miljøet blir å slå sammen alle datasettene og lage en robust &lt;a href="https://tommyodland.com/tools/salary.html"&gt;lønnskalulator&lt;/a&gt;.
Den er robust fordi den (1) baserer seg på nesten ti tusen observasjoner og (2) tar automatisk høyde for lønnsvekst i fremtiden.&lt;/p&gt;
&lt;h1 id="datasett"&gt;Datasett&lt;/h1&gt;
&lt;p&gt;Det er fem datasett for fem ulike år.
“Data engineering” er alltid mer jobb enn man håper, og innsatsen for å slå sammen disse datasettene var ikke noe unntak.
Jeg måtte:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sammenstille lønnstall på format som &lt;code&gt;600 000&lt;/code&gt; med &lt;code&gt;600.000,00 kr&lt;/code&gt; og lignende.&lt;/li&gt;
&lt;li&gt;Sammenstille små variasjoner i spørsmålene fra år til år, f.eks. &lt;code&gt;I hvilket fylke ligger jobben din?&lt;/code&gt; og &lt;code&gt;I hvilket fylke jobber du?&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ta hensyn til at det noen år var spurt om lønn med bonus og andre år lønn uten bonus. Dette fikser jeg med en latent variabel i modelleringen, men vi går ikke inn på det i denne artikkelen.&lt;/li&gt;
&lt;li&gt;Fjerne tvilsomme observasjoner (omtrent 1.5% av dataene, der lønn var under 400 &lt;span class="caps"&gt;TNOK&lt;/span&gt; eller over 2000 &lt;span class="caps"&gt;TNOK&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;Noen har sannsynligvis tolket spørsmålet “Hvor mange års relevant, formell utdannelse har du?” til å inkludere barneskolen, ungdomsskolen og &lt;span class="caps"&gt;VGS&lt;/span&gt;. Dette må fikses etter beste evne (jeg håper og tror ingen har vært student på et universitet i mer enn femten år).&lt;/li&gt;
&lt;li&gt;Ta hensyn til &lt;a href="https://no.wikipedia.org/wiki/Regionreformen_i_Norge"&gt;regionreformen&lt;/a&gt; og dens delvise omgjørelse.&lt;/li&gt;
&lt;li&gt;Slå sammen fagdisipliner, f.eks. var &lt;code&gt;arkitektur&lt;/code&gt; et alternativ noen år, mens det het &lt;code&gt;arkitekt&lt;/code&gt; andre år.&lt;/li&gt;
&lt;li&gt;Slå sammen spørsmål: tidligere var jobb (in-house, konsulent, osv.) og sektor (privat, offentlig) to spørsmål—nå er det slått sammen til ett spørsmål.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Etter en grundig datavask står vi igjen med 9610 observasjoner.
En del av disse er nok de samme personene som svarer på nytt, men det tenker jeg at er greit i denne sammenhengen.
Som alltid er det masse forbehold: dataene er selvrapporterte, kode24’s lesere er ikke representative for utviklere generelt, osv.
Nedenfor er lønn og erfaring visualisert for alle årene.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/kode24_lonn_2025/erfaring_vs_lønn.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 600px;"/&gt;&lt;/p&gt;
&lt;p&gt;Medianlønna har økt de siste fem årene, men det har også gjennomsnittlig erfaring blant kode24’s lesere.&lt;/p&gt;
&lt;table class="table-striped table-hover table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: right;"&gt;  år&lt;/th&gt;
&lt;th style="text-align: right;"&gt;  erfaring&lt;/th&gt;
&lt;th style="text-align: right;"&gt;  lønn&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2021&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       8.2&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   690&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2022&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       8.3&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   725&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2023&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       8.8&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   790&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2024&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       8.5&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   850&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;2025&lt;/td&gt;
&lt;td style="text-align: right;"&gt;       9.4&lt;/td&gt;
&lt;td style="text-align: right;"&gt;   880&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;En regresjon på log-lønna avslører at den gjennomsnittlige lønnsveksten har vært omtrent 6.4% de siste årene, men dette tar ikke hensyn til at erfaringen også har økt.
Om vi korrigerer for dette blir &lt;strong&gt;lønnsøkningen omtrent 5.1% per år&lt;/strong&gt;.&lt;/p&gt;
&lt;h1 id="lnnskalkulatorer"&gt;Lønnskalkulatorer&lt;/h1&gt;
&lt;p&gt;En kalkulator implementerer vanligvis en kjent formel.
For eksempel implementerer en skattekalkulator formelen for skatt (noe som kan være &lt;a href="https://tommyodland.com/articles/2024/smooth-taxes-without-brackets"&gt;utfordrende&lt;/a&gt; å finne ut av).
På samme måte kan en lønnskalkulator implementere “formelen for lønn”, men den vet vi ikke hva er.
Derfor må vi først bruke en modell til å lære en formel fra datasettet, og deretter implementere denne.
Et alternativ er å ikke implementere noen formel i det hele tatt, men heller bruke en såkalt &lt;em&gt;ikke-parametrisk modell&lt;/em&gt; og vise brukeren sammenlignbare personer.
Vi skal se på disse to alternativene i denne artikkelen, men det finnes andre muligheter også.&lt;/p&gt;
&lt;p&gt;Uansett fremgangsmåte bør en god kalkulator:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Predikere godt.&lt;/strong&gt; Estimert lønn bør matche faktisk lønn for så mange som mulig. Dette kan kryssvalideres.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kommunisere usikkerheten.&lt;/strong&gt; I dette datasettet har hver prediksjon stor usikkerhet, og det må brukeren forstå.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ikke-parametriske-modeller"&gt;Ikke-parametriske modeller&lt;/h2&gt;
&lt;p&gt;Anta at en typisk utvikler ønsker å vite hva han eller hun kan forvente å tjene:&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="s1"&gt;'år'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fylke'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Oslo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jobb'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'in-house, privat sektor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'erfaring'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'utdannelse'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fag'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Fullstack'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="k-nrmeste-naboer"&gt;K-nærmeste naboer&lt;/h3&gt;
&lt;p&gt;Vi kan bruke &lt;a href="https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm"&gt;K-nærmeste naboer&lt;/a&gt; med &lt;span class="math"&gt;\(k=10\)&lt;/span&gt; på 2025-data for å vise ti personer som ligner.
Disse er&lt;/p&gt;
&lt;table class="table-striped table-hover table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: right;"&gt;  erfaring&lt;/th&gt;
&lt;th style="text-align: right;"&gt;  utdannelse&lt;/th&gt;
&lt;th style="text-align: left;"&gt;fylke  &lt;/th&gt;
&lt;th style="text-align: left;"&gt;jobb                    &lt;/th&gt;
&lt;th style="text-align: left;"&gt;fag      &lt;/th&gt;
&lt;th style="text-align: right;"&gt;    lønn&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         5&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 775    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         7&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 803    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 819    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         5&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 877    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 895    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 900    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 914.375&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         5&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 950    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1000    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1400    &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Her var vi heldige, fordi det er mange personer i Oslo med lignende egenskaper.
Medianlønna blant disse ti personene er 897 &lt;span class="caps"&gt;TNOK&lt;/span&gt;, og 80% ligger i intervallet mellom 800 og 1040 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="beslutningstre"&gt;Beslutningstre&lt;/h3&gt;
&lt;p&gt;Dersom vi bruker et &lt;a href="https://en.wikipedia.org/wiki/Decision_tree"&gt;beslutningstre&lt;/a&gt; i stedet, får vi følgende tabell.
Modellen er satt til å alltid returnere minst ti personer, og for denne spørringen returnerer den 15.
Sammenlignet med K-nærmeste naboer er beslutningstreet mer opptatt av erfaring, og mindre opptatt av fag og jobb.&lt;/p&gt;
&lt;table class="table-striped table-hover table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: right;"&gt;  erfaring&lt;/th&gt;
&lt;th style="text-align: right;"&gt;  utdannelse&lt;/th&gt;
&lt;th style="text-align: left;"&gt;fylke  &lt;/th&gt;
&lt;th style="text-align: left;"&gt;jobb                                  &lt;/th&gt;
&lt;th style="text-align: left;"&gt;fag                      &lt;/th&gt;
&lt;th style="text-align: right;"&gt;    lønn&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;frilans / selvstendig næringsdrivende&lt;/td&gt;
&lt;td style="text-align: left;"&gt;DevOps og Automatisering&lt;/td&gt;
&lt;td style="text-align: right;"&gt; 500    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;span class="caps"&gt;UX&lt;/span&gt; og Design            &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 650    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Ledelse                  &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 735    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Frontend                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 780    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 819    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Testing                  &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 830    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 895    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Frontend                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 900    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 900    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 914.375&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 918    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Frontend                &lt;/td&gt;
&lt;td style="text-align: right;"&gt; 940    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt;1000    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;in-house, privat sektor              &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt;1400    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: right;"&gt;         6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;           3&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Oslo    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;konsulent                            &lt;/td&gt;
&lt;td style="text-align: left;"&gt;Fullstack                &lt;/td&gt;
&lt;td style="text-align: right;"&gt;1400    &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Medianlønna blant disse 15 personene er 900 &lt;span class="caps"&gt;TNOK&lt;/span&gt;, og 80% ligger i intervallet mellom 684 og 1240 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="sammenligning-k-nrmeste-naboer-vs-beslutningstre"&gt;Sammenligning: K-nærmeste naboer vs. beslutningstre&lt;/h3&gt;
&lt;p&gt;Vi kan sammenligne Mean Absolute Error (&lt;span class="caps"&gt;MAE&lt;/span&gt;) på en 10-folds kryssvalidering for å se hvor mange personer modellen bør returnere for å gi gode prediksjoner.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/kode24_lonn_2025/knn_vs_tree.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 600px;"/&gt;&lt;/p&gt;
&lt;p&gt;Vi observerer at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Beslutningstrær er klart bedre enn K-nærmeste naboer, med lavere &lt;span class="caps"&gt;MAE&lt;/span&gt; over hele linja.&lt;/li&gt;
&lt;li&gt;Så lenge vi returnerer omtrent 20 personer så får vi gode prediksjoner.&lt;/li&gt;
&lt;li&gt;Jo flere personer vi returnerer, desto bedre kan vi estimere et intervall og si noe om lønnspennet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Grunnen til at beslutningstrær er bedre enn K-nærmeste naboer er fordi førstnevnte automatisk velger hvilke variabler som er viktige (erfaring er helt klart viktigst), mens sistnevnte må gå like langt ut i alle dimensjoner for å finne naboer.&lt;/p&gt;
&lt;p&gt;Både K-nærmeste naboer og beslutningstrær har noen felles svakheter i denne problemstillingen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nær endepunktene i hver variabel er de nødt til å se innover for å gjøre prediksjoner.
  For eksempel, dersom man har 45 års erfaring vil modellen ikke finne noen personer som har &lt;em&gt;mer&lt;/em&gt; erfaring, kun personer som har &lt;em&gt;mindre&lt;/em&gt; erfaring.
  Det samme gjelder de som har null år med erfaring eller null år med utdannelse.
  Dette er spesielt kritisk i høye dimensjoner, fordi da er sannsynligvis &lt;a href="https://www.johndcook.com/blog/2011/09/01/multivariate-normal-shell/"&gt;alle datapunktene nær endepunktene&lt;/a&gt;—jo flere ting vi måler, desto mer er alle mennesker ulike på hver sin måte!&lt;/li&gt;
&lt;li&gt;Vi brukte 2025-data til nå, men vi ønsker å bruke hele datasettet. 
  Vi vet at tidligere år har lavere lønn på grunn av generell lønnsøkning i samfunnet, men 2025-tallene er nær endepunktet på variabelen “år”.
  Derfor vil modellene ofte kun hente informasjon fra 2025 og ikke bruke hele datasettet.
  Begge modellen er lokale heller enn globale, og evner ikke å bruke informasjon fra tidligere år på en god måte.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="parametriske-modeller"&gt;Parametriske modeller&lt;/h2&gt;
&lt;p&gt;En parametrisk modell lærer parametrene i en formel som beskriver et datasett.
Fordelen er at hele datasettet kan komprimeres ned til én kort formel som beskriver sammenhengene.
Ulempen er at man må selv spesifisere en fornuftig formel.
Parametriske modeller er globale og ekstrapolerer vanligvis godt, og dette er en fordel i vår sammenheng—spesielt med tanke på årstall inn i fremtiden og lønnsinflasjon.&lt;/p&gt;
&lt;p&gt;Første idé er å bruke en &lt;a href="https://en.wikipedia.org/wiki/Ridge_regression"&gt;Ridge-modell&lt;/a&gt; og minimere&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{minimize} \, &amp;amp;\lVert \text{lønn} - \boldsymbol{\mu} \rVert_2^2 + \alpha \lVert \boldsymbol{\beta} \rVert_2^2 \\
\mu &amp;amp;= \beta_0 + f(\text{erfaring}) + \beta_{\text{utdannelse}} \text{utdannelse} + \beta_{\text{fylke}} + \beta_{\text{jobb}} + \beta_{\text{fag}} \\
f(z) &amp;amp;= \beta_1 z + \beta_2 z^2 + \beta_3 z^3 + \cdots
\end{align*}&lt;/div&gt;
&lt;p&gt;og dette er et godt utgangspunkt som gir helt kurante resultater.
Høyere-ordens polynomer er vanligvis ingen god idé, fordi de kan overtilpasse og svinge voldsomt.
Det er derfor viktig å plotte polynomet (noe som ble gjort men ikke er med i artikkelen).&lt;/p&gt;
&lt;p&gt;Hvordan kan vi forbedre denne modellen?
For det første vil vi finne medianlønna heller enn gjennomsnittslønna, fordi medianen er mindre påvirket av ekstreme observasjoner og er ofte et bedre valg i slike sammenhenger.
Det var derfor vi brukte Mean Absolute Error (&lt;span class="caps"&gt;MAE&lt;/span&gt;) heller enn Mean Squared Error (&lt;span class="caps"&gt;MSE&lt;/span&gt;) tidligere; &lt;a href="https://tommyodland.com/articles/2024/the-average-value"&gt;&lt;span class="caps"&gt;MAE&lt;/span&gt; er minimert av medianen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For det andre er en tradisjonell regresjonsmodell påvirket av feil i absolutte tall, mens multiplikative feil gir mer mening i lønnssammenheng.
Med andre ord, å bomme med 100 &lt;span class="caps"&gt;TNOK&lt;/span&gt; når lønna er 500 &lt;span class="caps"&gt;TNOK&lt;/span&gt; er en stor feil, mens å bomme med 100 &lt;span class="caps"&gt;TNOK&lt;/span&gt; når lønna er 1200 &lt;span class="caps"&gt;TNOK&lt;/span&gt; er ikke like ille, fordi det er en mindre multiplikativ feil.&lt;/p&gt;
&lt;p&gt;Vi endrer modellen til å bruke en &lt;span class="math"&gt;\(\ell_1\)&lt;/span&gt;-norm på likelihood-leddet og tilpasse log-lønn heller enn lønn:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\text{minimize} \, &amp;amp;\lVert \log \left( \text{lønn} \right) - \boldsymbol{\mu} \rVert_1 + \alpha \lVert \boldsymbol{\beta} \rVert_2^2
\end{align*}&lt;/div&gt;
&lt;p&gt;Dette minner om en &lt;a href="https://en.wikipedia.org/wiki/Lasso_(statistics)"&gt;Lasso&lt;/a&gt;, men med normene byttet om: vi har &lt;span class="math"&gt;\(\ell_1\)&lt;/span&gt;-norm på likelihood-leddet og &lt;span class="math"&gt;\(\ell_2\)&lt;/span&gt;-norm på regulariseringsleddet.
Her er koeffisientene i modellen (klikk på figuren for å se større versjon):&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_effects.png"&gt;
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_effects.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 780px;"/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Legg merke til at lønnsveksten estimeres til 5.1% per år.&lt;/p&gt;
&lt;p&gt;Å sammenligne mellom kategorier innebærer å konvertere prosentene til relative tall, ikke å legge dem sammen additivt.
For eksempel, dersom man går fra en in-house offentlig stilling til å bli konsulent, kan man forvente en lønnsøkning på omtrent &lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\frac{1 + 5.9\%}{1 - 5.4\%} = \frac{1.059}{0.946}= 1.119 = 11.9\% .
\end{align*}&lt;/div&gt;
&lt;p&gt;Her er residualene til modellen:&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/kode24_lonn_2025/log_linear_residuals.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 740px;"/&gt;&lt;/p&gt;
&lt;p&gt;Dette er relativt pene residualer, fordi det ikke er stor endring i residualene over lønnsintervallet, såkalt heteroskadist… hetroskedasisti…. &lt;a href="https://no.wikipedia.org/wiki/Heteroskedastisitet"&gt;heteroskedastisitet&lt;/a&gt;.
Polynomet som ble brukt var et fjerdegradspolynom, som på grunn av regularisering oppfører seg pent når erfaring er mellom null og førti år.&lt;/p&gt;
&lt;p&gt;La oss kjøre vår “typiske” utvikler gjennom alle tre modellene:&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="s1"&gt;'år'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fylke'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Oslo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jobb'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'in-house, privat sektor'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s1"&gt;'erfaring'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'utdannelse'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fag'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Fullstack'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Vi observerer noe ulike estimater av medianlønn og intervaller fra de ulike modellene:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;span class="caps"&gt;KNN&lt;/span&gt;&lt;/strong&gt;: 897 &lt;span class="caps"&gt;TNOK&lt;/span&gt; med 80% intervall mellom 800 og 1040 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tre&lt;/strong&gt;: 900 &lt;span class="caps"&gt;TNOK&lt;/span&gt; med 80% intervall mellom 684 og 1240 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lineær modell&lt;/strong&gt;: 918 &lt;span class="caps"&gt;TNOK&lt;/span&gt; med 80% intervall mellom 748 og 1136 &lt;span class="caps"&gt;TNOK&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Intervallene fra de ikke-parametriske modellene (&lt;span class="caps"&gt;KNN&lt;/span&gt; og tre) er mindre robuste, ettersom de er basert på henholdsvis 10 og 15 observasjoner.
Intervallet fra den lineære modellen er basert på alle datapunktene, og har høyere kvalitet.&lt;/p&gt;
&lt;p&gt;La oss nå sammenligne &lt;span class="caps"&gt;MAE&lt;/span&gt; for alle modellene, samt en svak dummy-modell som alltid predikerer medianen og en kraftig gradient boosting modell.&lt;/p&gt;
&lt;table class="table-striped table-hover table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt; Modell / Metrikk&lt;/th&gt;
&lt;th&gt; &lt;span class="caps"&gt;MAE&lt;/span&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dummy            &lt;/td&gt;
&lt;td&gt;202  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span class="caps"&gt;KNN&lt;/span&gt;              &lt;/td&gt;
&lt;td&gt;152  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tre              &lt;/td&gt;
&lt;td&gt;128  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lineær            &lt;/td&gt;
&lt;td&gt;119  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradient boosting&lt;/td&gt;
&lt;td&gt;114  &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Gradient boosting har lavest &lt;span class="caps"&gt;MAE&lt;/span&gt;, men den lineære modellen er bare marginalt verre.
Likevel har den noen klare fordeler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Det estimerte intervallet (usikkerheten) er nok mer robust og realistisk.&lt;/li&gt;
&lt;li&gt;Den er veldig enkel å implementere i Javascript. Man bruker bare formelen.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="oppsummering"&gt;Oppsummering&lt;/h1&gt;
&lt;p&gt;Kode24 har gjort en kjempejobb med å samle inn og publisere data på utvikleres lønn.
I denne artikkelen kombinerte vi fem år med lønnsdata til ett datasett med 9610 observasjoner, etter datavask og sammenstilling av variabler på tvers av år.&lt;/p&gt;
&lt;p&gt;Vi sammenlignet to ulike tilnærminger til lønnskalkulatorer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ikke-parametriske modeller&lt;/strong&gt; (&lt;span class="caps"&gt;KNN&lt;/span&gt; og beslutningstrær) som viser faktiske, sammenlignbare personer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parametriske modeller&lt;/strong&gt; (log-lineær regresjon med &lt;span class="math"&gt;\(\ell_1\)&lt;/span&gt;-loss) som bruker en global formel&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Den enkle log-lineære modellen (&lt;span class="caps"&gt;MAE&lt;/span&gt;: 119) presterte bedre enn de to ikke-parametriske modellene, fordi &lt;span class="caps"&gt;MAE&lt;/span&gt; var lavere og intervallene var mer robuste. 
Faktisk hadde modellen nesten like lav &lt;span class="caps"&gt;MAE&lt;/span&gt; som gradient boosting (&lt;span class="caps"&gt;MAE&lt;/span&gt;: 114), men med fordelen av å være tolkbar og enkel å implementere i JavaScript.
Beslutningstrær var gode for å finne sammenlignbare personer, betydelig bedre enn K-nærmeste naboer.&lt;/p&gt;
&lt;p&gt;Når det gjelder metodikk for lønnskalkulatorer er lærdommen klar: prioriter &lt;a href="https://eugeneyan.com/writing/simplicity/"&gt;enkelhet&lt;/a&gt; og transparens.
Brukere trenger to ting: (1) fornuftige prediksjoner og (2) ærlig kommunikasjon om usikkerheten. 
&lt;a href="https://tommyodland.com/tools/salary.html"&gt;Kalkulatoren&lt;/a&gt; implementerer den enkle lineære modellen.&lt;/p&gt;
&lt;p&gt;Selv om jeg fulgte &lt;a href="https://tommyodland.com/articles/2024/the-ten-commandments-of-data-science"&gt;bud (5) og bud (9)&lt;/a&gt; har jeg dårlig samvittighet for å ha brukt et fjerdegradspolynom i modelleringen uten progressiv regularisering—men jeg slapp unna med det på partial dependence plottet. Aldri igjen.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Wed, 17 Sep 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-09-17:/articles/2025/lonna-til-norske-utviklere-i-2025</guid><category>articles</category><category>datascience</category></item><item><title>People rating people</title><link>https://tommyodland.com/articles/2025/people-rating-people</link><description>&lt;p&gt;Ratings and rankings are everywhere in daily life.
We rank &lt;a href="https://tommyodland.com/articles/2020/modeling-the-outcome-of-football-matches"&gt;sports teams&lt;/a&gt;, chess players, movies and &lt;span class="caps"&gt;TV&lt;/span&gt;-shows, &lt;a href="https://tommyodland.com/articles/2023/ranking-doctors"&gt;doctors&lt;/a&gt;, local businesses, and sometimes even each other.&lt;/p&gt;
&lt;p&gt;This article is about computing an overall rating in a setting where people have individually rated each other.
Examples include dating apps like Tinder, e-commerce websites where people act as both sellers and buyers, and other platforms where we want to quantify the trustworthiness or quality of members.&lt;/p&gt;
&lt;p&gt;We distinguish between two scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Symmetry&lt;/strong&gt;: if person &lt;span class="math"&gt;\(a\)&lt;/span&gt; rates &lt;span class="math"&gt;\(b\)&lt;/span&gt;, then &lt;span class="math"&gt;\(b\)&lt;/span&gt; must also rate &lt;span class="math"&gt;\(a\)&lt;/span&gt;. Their rating values may differ.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No symmetry&lt;/strong&gt;: if person &lt;span class="math"&gt;\(a\)&lt;/span&gt; rates &lt;span class="math"&gt;\(b\)&lt;/span&gt;, then it does not necessarily follow that &lt;span class="math"&gt;\(b\)&lt;/span&gt; rates &lt;span class="math"&gt;\(a\)&lt;/span&gt; back.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this article we’ll consider the more general &lt;strong&gt;non-symmetric&lt;/strong&gt; scenario.
Given individual ratings that are either &lt;em&gt;positive&lt;/em&gt; or &lt;em&gt;negative&lt;/em&gt;, the goal is to produce an overall rating of everyone.
The figure below shows 5 people, where blue arrows indicate positive ratings and red arrows indicate negative ratings.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/people_rating_people/people_ratings.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 500px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;In the figure above, we expect &lt;span class="math"&gt;\(a\)&lt;/span&gt; to be highly rated and &lt;span class="math"&gt;\(c\)&lt;/span&gt; to be poorly rated.&lt;/p&gt;
&lt;h1 id="simple-aggregation"&gt;Simple aggregation&lt;/h1&gt;
&lt;p&gt;The simplest thing we can do is to count the number of ratings and create a ratio &lt;span class="math"&gt;\(r_i\)&lt;/span&gt; between &lt;span class="math"&gt;\(0\)&lt;/span&gt; and &lt;span class="math"&gt;\(1\)&lt;/span&gt; for each person.&lt;/p&gt;
&lt;p&gt;We define a binary matrix &lt;span class="math"&gt;\(P\)&lt;/span&gt; consisting of positive ratings and a matrix &lt;span class="math"&gt;\(N\)&lt;/span&gt; of negative ratings.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
P_{ij} &amp;amp;= \begin{cases}
1 &amp;amp; \text{if person } i \text{ is rated positively by person } j \\
0 &amp;amp; \text{otherwise}
\end{cases} \\
N_{ij} &amp;amp;= \begin{cases}
1 &amp;amp; \text{if person } i \text{ is rated negatively by person } j \\
0 &amp;amp; \text{otherwise}
\end{cases}
\end{align*}&lt;/div&gt;
&lt;p&gt;The rating vector &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt; is computed by summing positive ratings and dividing by total ratings.
One issue with the naive approach is that someone who has few ratings might end up with a total rating of &lt;span class="math"&gt;\(0\)&lt;/span&gt; or &lt;span class="math"&gt;\(1\)&lt;/span&gt;, so we employ the &lt;a href="https://en.wikipedia.org/wiki/Rule_of_succession"&gt;rule of succession&lt;/a&gt; by adding two dummy ratings: one positive and one negative.
The equation becomes&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
r_i &amp;amp;= \frac{1 + \sum_j P_{ij}}{2 + \sum_j P_{ij} + \sum_j N_{ij}}
\end{align*}&lt;/div&gt;
&lt;p&gt;Or alternatively, in matrix notation, where &lt;span class="math"&gt;\(\boldsymbol{1}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{2}\)&lt;/span&gt; denote vectors of ones and twos and division is entry-wise:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r} &amp;amp;= \frac{\boldsymbol{1} + P \boldsymbol{1}}{\boldsymbol{2} + P \boldsymbol{1} + N \boldsymbol{1}}
\end{align*}&lt;/div&gt;
&lt;p&gt;Here’s a piece of Python code that implements this computation on the graph structure shown in the initial figure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&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;np&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="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="mi"&gt;1&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- Positive ratings given to A&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;0&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;0&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="c1"&gt;# &amp;lt;- Positive ratings given to B&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;0&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;0&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="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="mi"&gt;1&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;0&lt;/span&gt;&lt;span class="p"&gt;],&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="mi"&gt;0&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;0&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="c1"&gt;# &amp;lt;- Positive ratings given to E&lt;/span&gt;
    &lt;span class="p"&gt;])&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;np&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="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;0&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;0&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="c1"&gt;# &amp;lt;- Negative ratings given to A&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;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="mi"&gt;0&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="c1"&gt;# &amp;lt;- Negative ratings given to B&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="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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&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="mi"&gt;0&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;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="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;0&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;0&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="c1"&gt;# &amp;lt;- Negative ratings given to E&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Simple aggregation&lt;/span&gt;
&lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0.83 0.33 0.2  0.5  0.67]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The results are superimposed on the graph in the figure below.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/people_rating_people/people_ratings_result_simple.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 500px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;The popular person &lt;span class="math"&gt;\(a\)&lt;/span&gt; gets the highest rating &lt;span class="math"&gt;\(0.83\)&lt;/span&gt; as expected, and unpopular person &lt;span class="math"&gt;\(c\)&lt;/span&gt; gets the lowest rating &lt;span class="math"&gt;\(0.2\)&lt;/span&gt; as expected.&lt;/p&gt;
&lt;h1 id="weighted-aggregation"&gt;Weighted aggregation&lt;/h1&gt;
&lt;p&gt;Perhaps all users in a system should not be considered equally trustworthy.
In other words, if &lt;span class="math"&gt;\(a\)&lt;/span&gt; rates &lt;span class="math"&gt;\(b\)&lt;/span&gt; positively, then our opinion of &lt;span class="math"&gt;\(b\)&lt;/span&gt; &lt;em&gt;might also depend on &lt;span class="math"&gt;\(a\)&lt;/span&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(a\)&lt;/span&gt; is a long-time user, who is positively rated by others and who has given balanced ratings to others, then we should trust &lt;span class="math"&gt;\(a\)&lt;/span&gt;’s ratings.&lt;/li&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(a\)&lt;/span&gt; is a new user, or is negatively rated by others, or rates everyone negatively, then we should perhaps &lt;em&gt;not&lt;/em&gt; trust &lt;span class="math"&gt;\(a\)&lt;/span&gt;’s ratings as much.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We weight each user with a weight &lt;span class="math"&gt;\(w_j \in [0, 1]\)&lt;/span&gt; before we aggregate their individual ratings:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
r_i &amp;amp;= \frac{1 + \sum_j P_{ij} w_j}{2 + \sum_j P_{ij} w_j + \sum_j N_{ij} w_j}
\end{align*}&lt;/div&gt;
&lt;p&gt;How do we choose the weights &lt;span class="math"&gt;\(w_j\)&lt;/span&gt;?
There are many possibilities, but the weights &lt;span class="math"&gt;\(w_j\)&lt;/span&gt; must somehow represent the trustworthiness of person &lt;span class="math"&gt;\(j\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="weighting-by-discrimination"&gt;Weighting by discrimination&lt;/h2&gt;
&lt;p&gt;One idea is to look at how much &lt;em&gt;discrimination&lt;/em&gt; each user applies.
Whose ratings do you trust more: someone who rates everyone positively, or someone who rates half the population negatively and the other half positively?
If you agree with me that the second person provides more useful information, then one option is to compute a scaled version of the &lt;a href="https://en.wikipedia.org/wiki/Entropy_(information_theory)"&gt;entropy&lt;/a&gt;:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
H(p, n) = \frac{1}{\ln 2} \left[ \frac{p}{p+n} \ln \left( \frac{p+n}{p} \right) + \frac{n}{p+n} \ln \left(\frac{p+n}{n}\right) \right],
\end{align*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(n\)&lt;/span&gt; is the number of negative ratings the person has given and &lt;span class="math"&gt;\(p\)&lt;/span&gt; is the number of positive ratings.&lt;/p&gt;
&lt;p&gt;Observe that &lt;span class="math"&gt;\(H(p=k, n=k) = 1\)&lt;/span&gt; for all &lt;span class="math"&gt;\(k \geq 1\)&lt;/span&gt; and that &lt;span class="math"&gt;\(H(p=k, n=0) = H(p=0, n=k) = 0\)&lt;/span&gt; for all &lt;span class="math"&gt;\(k \geq 1\)&lt;/span&gt;.
In other words, this weighting function accomplishes what we set out to do: users with high discriminatory power are given a weight of one, while users who show no discrimination in their ratings are given a weight of zero.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy.stats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;entropy&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;H&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&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;entropy&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 1.0&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;H&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;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# 0.0&lt;/span&gt;

&lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&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;axis&lt;/span&gt;&lt;span class="o"&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Weighted positive and negative sums&lt;/span&gt;
&lt;span class="n"&gt;P_w&lt;/span&gt; &lt;span class="o"&gt;=&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;weights&lt;/span&gt;
&lt;span class="n"&gt;N_w&lt;/span&gt; &lt;span class="o"&gt;=&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;weights&lt;/span&gt;

&lt;span class="c1"&gt;# Weighted ratings&lt;/span&gt;
&lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0.83 0.34 0.21 0.49 0.67]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this particular problem instance, the results do not differ much from the previous ratings.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/people_rating_people/people_ratings_result_weighted.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 500px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Entropy is by no means the only possible quality measure—it depends strongly on the specific use-case.
A number of more complicated weighting functions could be used.
For instance, we could also weight a user by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;how many ratings they have given in total&lt;/li&gt;
&lt;li&gt;how long they have been users in the system/platform&lt;/li&gt;
&lt;li&gt;how much information they have on their profile&lt;/li&gt;
&lt;li&gt;how well they themselves are rated by others.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these signal high levels of trustworthiness.
We will now pursue option (4): we’ll weight each person’s ratings by &lt;em&gt;how highly they themselves are rated&lt;/em&gt;.&lt;/p&gt;
&lt;h1 id="weighted-aggregation-by-rating"&gt;Weighted aggregation by rating&lt;/h1&gt;
&lt;p&gt;The final model that we’ll consider is the most advanced one.
The idea is that the rating person &lt;span class="math"&gt;\(a\)&lt;/span&gt; gives to person &lt;span class="math"&gt;\(b\)&lt;/span&gt; should be weighted by &lt;em&gt;the rating person &lt;span class="math"&gt;\(a\)&lt;/span&gt; has&lt;/em&gt;.
Building on the matrix notation we established earlier, we write the equation as:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r} &amp;amp;= \frac{\boldsymbol{1} + P \boldsymbol{r}}{\boldsymbol{2} + P \boldsymbol{r} + N \boldsymbol{r}}
\end{align*}&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;In an e-commerce platform, this means that if &lt;span class="math"&gt;\(a\)&lt;/span&gt; is highly rated and gives a positive rating, then that is worth more than a positive rating from a user with mixed or negative ratings.&lt;/li&gt;
&lt;li&gt;In a dating app like Tinder where ratings signify attractiveness, the interpretation is that if someone who is attractive rates us favorably, then that rating is worth more than a rating from someone who is not considered attractive by others.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice the recursive definition: to weight the ratings given by person &lt;span class="math"&gt;\(a\)&lt;/span&gt; to others, we need to know the overall rating of person &lt;span class="math"&gt;\(a\)&lt;/span&gt;—but that quantity is exactly what we’re trying to compute in the first place!&lt;/p&gt;
&lt;h2 id="solution-by-fixed-point-iteration"&gt;Solution by fixed-point iteration&lt;/h2&gt;
&lt;p&gt;The simplest and fastest way to compute &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt; is to iterate the equation until it converges.
Starting with an initial guess &lt;span class="math"&gt;\(\boldsymbol{r}_0 = \boldsymbol{1} / 2\)&lt;/span&gt; we compute
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r}_{k+1} &amp;amp;= \frac{\boldsymbol{1} + P \boldsymbol{r}_{k}}{\boldsymbol{2} + P \boldsymbol{r}_{k} + N \boldsymbol{r}_{k}}
\end{align*}&lt;/div&gt;
&lt;p&gt;
repeatedly until convergence, as measured by &lt;span class="math"&gt;\(\lVert \boldsymbol{r}_{k+1} - \boldsymbol{r}_{k}  \rVert &amp;lt; \epsilon\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;epsilon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1e-12&lt;/span&gt;
&lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&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;shape&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;2&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;iteration&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;99&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;=&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;P&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;ratings&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;P&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;+&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;ratings&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ratings&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;epsilon&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;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_ratings&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0.74 0.44 0.29 0.42 0.63]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Convergence is linear, and after 13 iterations the procedure terminates.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/people_rating_people/people_ratings_result.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 500px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;All three ratings induce rankings on the people, and they are compared in the figure below.
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/people_rating_people/people_rating_people_results.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 400px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Notice how:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Person &lt;span class="math"&gt;\(b\)&lt;/span&gt; moves up compared to the simple rating. He is given a negative rating by &lt;span class="math"&gt;\(c\)&lt;/span&gt;, but &lt;span class="math"&gt;\(c\)&lt;/span&gt; has a low overall rating and this lessens the weight given to &lt;span class="math"&gt;\(c\)&lt;/span&gt;’s opinion of &lt;span class="math"&gt;\(b\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Person &lt;span class="math"&gt;\(d\)&lt;/span&gt; moves down, because the negative rating from &lt;span class="math"&gt;\(a\)&lt;/span&gt; matters a lot and the positive rating from &lt;span class="math"&gt;\(c\)&lt;/span&gt; matters little.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="summary-and-references"&gt;Summary and references&lt;/h2&gt;
&lt;p&gt;Rating and ranking is interesting because it is part science and part art.
There’s no fixed answer: every scenario is different and must be modeled appropriately.&lt;/p&gt;
&lt;p&gt;The problem considered in this article, where people rate people, is special in two ways: (1) there is just one group and (2) ratings are not symmetric.
The defining equation is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r} &amp;amp;= \frac{\boldsymbol{1} + P \boldsymbol{r}}{\boldsymbol{2} + P \boldsymbol{r} + N \boldsymbol{r}},
\end{align*}&lt;/div&gt;
&lt;p&gt;
which is non-linear in the vector &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt; and may contain millions of unknown variables.
Despite this, it’s solved by fixed-point iteration in a few seconds on a laptop.&lt;/p&gt;
&lt;p&gt;I’ve previously written about how &lt;a href="https://tommyodland.com/articles/2023/ranking-doctors"&gt;patients can rate doctors&lt;/a&gt;.
In that problem there were two separate groups: doctors and patients.
One group rates the other group.
I’ve also written about &lt;a href="https://tommyodland.com/articles/2020/modeling-the-outcome-of-football-matches"&gt;rating football teams&lt;/a&gt;, which is a symmetric problem.
If team &lt;span class="math"&gt;\(a\)&lt;/span&gt; plays &lt;span class="math"&gt;\(b\)&lt;/span&gt;, then &lt;span class="math"&gt;\(b\)&lt;/span&gt; necessarily plays &lt;span class="math"&gt;\(a\)&lt;/span&gt; too.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/PageRank"&gt;PageRank&lt;/a&gt; algorithm ranks websites, and it’s what made Google famous in the late 90s.
The paper &lt;a href="https://projecteuclid.org/journals/internet-mathematics/volume-2/issue-1/A-Survey-on-PageRank-Computing/im/1128530802.full"&gt;A Survey on PageRank Computing&lt;/a&gt; is an interesting read.
Similar algorithms include &lt;a href="https://en.wikipedia.org/wiki/HITS_algorithm"&gt;&lt;span class="caps"&gt;HITS&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/SALSA_algorithm"&gt;&lt;span class="caps"&gt;SALSA&lt;/span&gt;&lt;/a&gt; and &lt;a href="https://nlp.stanford.edu/pubs/eigentrust.pdf"&gt;EigenTrust&lt;/a&gt;.
The book &lt;a href="https://www.amazon.com/Whos-1-Science-Rating-Ranking/dp/069116231X"&gt;Who’s #1?: The Science of Rating and Ranking&lt;/a&gt; gives an overview of simple methods, with a focus on sports.
When users interact with each other, the field is sometimes called &lt;a href="https://en.wikipedia.org/wiki/Reputation_system"&gt;reputation systems&lt;/a&gt;.
The paper &lt;a href="https://www.jstor.org/stable/4134013"&gt;The Digitization of Word of Mouth: Promise and Challenges of Online Feedback Mechanisms&lt;/a&gt; explores the online reputation mechanisms of the early internet. &lt;/p&gt;
&lt;p&gt;The ideas in the paper &lt;a href="https://snap.stanford.edu/class/cs224w-readings/guha04trust.pdf"&gt;Propagation of Trust and Distrust&lt;/a&gt; are similar to those expressed in this article, but the authors focus more on local propagation while we solve a global problem.
Reputation and ranking is closely related to content discovery, recommendation systems and &lt;a href="https://en.wikipedia.org/wiki/Collaborative_filtering"&gt;collaborative filtering&lt;/a&gt;, and the book &lt;a href="https://link.springer.com/book/10.1007/978-3-319-29659-3"&gt;Recommender Systems&lt;/a&gt; by Aggarwal is a handy reference.
This has social implications too, which the paper &lt;a href="https://arxiv.org/abs/1904.13316"&gt;On Social Machines for Algorithmic Regulation&lt;/a&gt; explores.&lt;/p&gt;
&lt;!--
https://snap.stanford.edu/class/cs224w-readings/lauterbach09trust.pdf
https://math.stackexchange.com/questions/5088792/convergence-of-fixed-point-iteration-x-frac1-px2-px-nx
https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0050843
https://arxiv.org/abs/2309.02413v2
https://arxiv.org/abs/1304.7921
--&gt;
&lt;hr/&gt;
&lt;h2 id="appendix-a-empirical-results"&gt;Appendix A: Empirical results&lt;/h2&gt;
&lt;p&gt;I created random sparse rating matrices for a million people.
I set the density to &lt;span class="math"&gt;\(p=10^{-5}\)&lt;/span&gt;, so each matrix has approximately &lt;span class="math"&gt;\(\left( 10^6 \right)^2 \times p = 10^7\)&lt;/span&gt; non-negative entries.
Convergence is shown in the figure below.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/people_rating_people/fixed_point_solver.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 600px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Below is the computation on the third largest strongly connected component of the &lt;a href="https://snap.stanford.edu/data/soc-sign-epinions.html"&gt;Epinions social network&lt;/a&gt; dataset.
The full dataset has 131,828 nodes and 841,372 and is solved quickly, but it’s too large to visualize in full.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/people_rating_people/ratings_epinions.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 450px; width: 95%;"/&gt;&lt;/p&gt;
&lt;h2 id="appendix-b-solution-by-newtons-method"&gt;Appendix B: Solution by Newton’s method&lt;/h2&gt;
&lt;p&gt;Newton’s method and its variants can be an alternative to fixed-point iteration.
While Newton’s method exhibits quadratic convergence (fixed-point iteration is linear), the time spent per iteration is so high that it ends up being an inferior choice in practice.
Also, if the starting point is not good (&lt;span class="math"&gt;\(\boldsymbol{r} = 1/2\)&lt;/span&gt; tends to work, while &lt;span class="math"&gt;\(\boldsymbol{r} = 0\)&lt;/span&gt; does not) the algorithm will fail.
Regardless, here’s how it can be done.&lt;/p&gt;
&lt;p&gt;The matrix equation
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r} &amp;amp;= \frac{1 + P \boldsymbol{r}}{2 + P \boldsymbol{r} + N \boldsymbol{r}},
\end{align*}&lt;/div&gt;
&lt;p&gt;
 is equivalent to solving
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
F(\boldsymbol{r}) = \boldsymbol{r} \odot \left( \boldsymbol{2} + P \boldsymbol{r} + N \boldsymbol{r} \right) - \left( \boldsymbol{1} + P \boldsymbol{r} \right) = \boldsymbol{0}
\end{align*}&lt;/div&gt;
&lt;p&gt;
which we can do with &lt;a href="https://en.wikipedia.org/wiki/Newton%27s_method#Multidimensional_formulations"&gt;Newton’s method&lt;/a&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{r}_{k+1} = \boldsymbol{r}_{k} - J(\boldsymbol{r}_{k})^{-1} F(\boldsymbol{r}_{k}).
\end{align*}&lt;/div&gt;
&lt;p&gt;To find the Jacobian matrix &lt;span class="math"&gt;\(J_{ij}(\boldsymbol{r}) = \partial F_i(\boldsymbol{r}) / \partial \boldsymbol{r}_{j}\)&lt;/span&gt;, we first write out the components of &lt;span class="math"&gt;\(F(\boldsymbol{r})\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
F_i(\boldsymbol{r}) = r_i \left( 2 + \sum_k P_{ik} r_k + \sum_k N_{ik} r_k\right) - 1 - \sum_k P_{ik} r_k
\end{align*}&lt;/div&gt;
&lt;p&gt;Differentiating with respect to &lt;span class="math"&gt;\(r_j\)&lt;/span&gt;, then switching back to matrix notation, we obtain:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
J(\boldsymbol{r}) = \partial_{r_j}  F_i(\boldsymbol{r}) &amp;amp; = 
\delta_{ij} \left( 2 + \sum_k P_{ik} r_k + \sum_k N_{ik} r_k\right) + r_i \left( P_{ij} + N_{ij} \right) - 0 - P_{ij} \\
&amp;amp;= 2 I + \operatorname{diag}(P \boldsymbol{r} + N \boldsymbol{r}) + \operatorname{diag}(\boldsymbol{r}) (P + N) - P
\end{align*}&lt;/div&gt;
&lt;p&gt;This can be implemented in Python as:&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="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;F_and_J&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Returns a tuple (F, J), with function value F and its jacobian J."""&lt;/span&gt;
    &lt;span class="n"&gt;Pr&lt;/span&gt; &lt;span class="o"&gt;=&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;ratings&lt;/span&gt;
    &lt;span class="n"&gt;Pr_Nr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pr&lt;/span&gt; &lt;span class="o"&gt;+&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;ratings&lt;/span&gt;
    &lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ratings&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;Pr_Nr&lt;/span&gt;&lt;span class="p"&gt;)&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;Pr&lt;/span&gt;
    &lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&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;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;
    &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diagonal&lt;/span&gt;&lt;span class="p"&gt;()&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="n"&gt;Pr_Nr&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;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt;

&lt;span class="n"&gt;epsilon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1e-12&lt;/span&gt;
&lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&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;shape&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;2&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;iteration&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;99&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F_and_J&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ratings&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;epsilon&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;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_ratings&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ratings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# [0.74 0.44 0.29 0.42 0.63] &amp;lt;- Same as fixed-point&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that the code above explicitly stores zeros (it does not use sparse matrices), and would therefore be unsuitable for large matrices.&lt;/p&gt;
&lt;h2 id="appendix-c-properties-of-the-fixed-point-iteration"&gt;Appendix C: Properties of the fixed-point iteration&lt;/h2&gt;
&lt;p&gt;In millions of computational experiments with random matrices &lt;span class="math"&gt;\(P\)&lt;/span&gt; and &lt;span class="math"&gt;\(N\)&lt;/span&gt;, the fixed-point iteration seemingly always converges to a unique fixed point.
For a long time I conjectured that the fixed point iteration always converges to a unique, attracting fixed point.
However, it turns out this conjecture is false.&lt;/p&gt;
&lt;p&gt;A counterexample is the following structure:
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/people_rating_people/counterexample_cycle.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 600px; width: 95%;"/&gt;
There are three groups and at least 16 people in each group.
Within each group, everyone rates everyone else negatively.
There are no self-ratings.&lt;/p&gt;
&lt;p&gt;With 16 people in each group, the algorithm cycles between &lt;span class="math"&gt;\((0.509, 0.366, 0.509)\)&lt;/span&gt; and &lt;span class="math"&gt;\((0.29, 0.585, 0.29)\)&lt;/span&gt; when started at a random &lt;span class="math"&gt;\(\boldsymbol{r}\)&lt;/span&gt;.
With 15 people in each group, the algorithm does not cycle, but will eventually converge.&lt;/p&gt;
&lt;h2 id="appendix-d-code"&gt;Appendix D: Code&lt;/h2&gt;
&lt;p&gt;Here is some Python 3.12 code for the model.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# Version: 2.1.3&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;RatingProblem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&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;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Initialize a problem given positive binary ratings P&lt;/span&gt;
&lt;span class="sd"&gt;        and negative binary ratings N."""&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;(&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;shape&lt;/span&gt; &lt;span class="o"&gt;==&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;shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&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;shape&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="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;P&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;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Create a random problem instance of size n."""&lt;/span&gt;
        &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create adjacency matrix with random density&lt;/span&gt;
        &lt;span class="n"&gt;density&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;adj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&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;density&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Zero out diagonal (can't rate yourself)&lt;/span&gt;
        &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill_diagonal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adj&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="c1"&gt;# Randomly split into positive and negative ratings&lt;/span&gt;
        &lt;span class="n"&gt;split&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&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;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&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;cls&lt;/span&gt;&lt;span class="p"&gt;(&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;adj&lt;/span&gt; &lt;span class="o"&gt;*&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;split&lt;/span&gt;&lt;span class="p"&gt;),&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;adj&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__call__&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;r&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&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;P&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;N&lt;/span&gt;
        &lt;span class="k"&gt;return&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;P&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;r&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;P&lt;/span&gt; &lt;span class="o"&gt;@&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;N&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;jacobian&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;r&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Compute the Jacobian matrix at r."""&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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;P&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&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;P&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;N&lt;/span&gt;
        &lt;span class="n"&gt;Pr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Nr&lt;/span&gt; &lt;span class="o"&gt;=&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;r&lt;/span&gt;&lt;span class="p"&gt;,&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;r&lt;/span&gt;

        &lt;span class="c1"&gt;# Vectorized jacobian computation&lt;/span&gt;
        &lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;=&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;Nr&lt;/span&gt;&lt;span class="p"&gt;)[:,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;P&lt;/span&gt; &lt;span class="o"&gt;-&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;Pr&lt;/span&gt;&lt;span class="p"&gt;)[:,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;
        &lt;span class="n"&gt;denominator&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;Pr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Nr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;solve&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;r_0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;epsilon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-12&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;"""Solve using fixed-point iteration."""&lt;/span&gt;
        &lt;span class="n"&gt;r_0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&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;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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;2&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r_0&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;r_0&lt;/span&gt;

        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r_0&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;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r_0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&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;iteration&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;99&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&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;ratings&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_ratings&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ratings&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;epsilon&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;ratings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_ratings&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ratings&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&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="c1"&gt;# Test that different starting points converge to the same value&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;test&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;10&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&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;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;problem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RatingProblem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&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;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;r1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s2&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;test&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1_000&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 04 Aug 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-08-04:/articles/2025/people-rating-people</guid><category>articles</category><category>mathematics</category></item><item><title>Hva kan jeg bruke matte til?</title><link>https://tommyodland.com/articles/2025/hva-kan-jeg-bruke-matte-til</link><description>&lt;p&gt;Elever spør ofte “&lt;em&gt;når får jeg bruk for dette?&lt;/em&gt;” eller “&lt;em&gt;hva skal jeg bruke matematikk til?&lt;/em&gt;“&lt;/p&gt;
&lt;p&gt;Det er et godt spørsmål som ofte går ubesvart.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/unsplash/mechanic.jpg" style="display: block; margin-left: auto; margin-right: auto; max-width: 400px; width: 90%;"/&gt;&lt;/p&gt;
&lt;p&gt;Det er flere måter å svare på slike spørsmål på.
Her en én type svar: en liste med 100 konkrete problemstillinger der matematikk er nyttig.&lt;/p&gt;
&lt;p&gt;Noen problemstillinger er relativt enkle, som å &lt;em&gt;&lt;a href="https://math.stackexchange.com/questions/74440/optimization-volume-of-a-box"&gt;bygge en boks med størst mulig volum&lt;/a&gt;&lt;/em&gt;.
Andre er vanskelige, som å &lt;em&gt;&lt;a href="https://www.youtube.com/watch?v=WXuK6gekU1Y"&gt;spille Go&lt;/a&gt;&lt;/em&gt;.
Å &lt;em&gt;&lt;a href="https://www.maa.org/programs/faculty-and-departments/classroom-capsules-and-notes/design-of-an-oscillating-sprinkler"&gt;vanne plenen&lt;/a&gt;&lt;/em&gt; fremstår som et tøyeste problem, men illustrerer hvor dypt man kan gå om man begynner å undersøke en problemstilling.
Andre er store og mer seriøse problemer, som å &lt;em&gt;&lt;a href="https://arxiv.org/abs/2106.09125"&gt;lande en rakett&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Det er på ingen måte ment å være en uttømmende eller spesielt seriøs liste :)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Applied-Combinatorics-Problem-Solving-Bradley/dp/0201129086"&gt;Telle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=WXuK6gekU1Y"&gt;Spille Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.stat.cmu.edu/~ryantibs/darts/"&gt;Spille dart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Sorting_algorithm"&gt;Sortere tall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://buy.inconvergent.net/"&gt;Lage kunst&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Løse Soduko&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2023/politikk-og-meningsrommet"&gt;Forstå politikk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.maa.org/programs/faculty-and-departments/classroom-capsules-and-notes/design-of-an-oscillating-sprinkler"&gt;Vanne plenen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2020/optimal-skill-tree-growth"&gt;Spille dataspill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Kalman_filter"&gt;Styre en missil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hdl.handle.net/11250/2832653"&gt;Lagre fiskemat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2106.09125"&gt;Lande en rakett&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Maze-solving_algorithm"&gt;Løse en labyrint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Maze_generation_algorithm"&gt;Lage en labyrint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Bill_Benter"&gt;Vedde på hester&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mpi-inf.mpg.de/fileadmin/inf/d1/teaching/winter18/Ideen/Materialien/Dantzig-Diet.pdf"&gt;Lage en matplan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martin.kleppmann.com/2011/03/07/accounting-for-computer-scientists.html"&gt;Føre et regnskap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jvns.ca/blog/2023/01/13/examples-of-floating-point-problems/"&gt;Regne med flyttall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Kelly_criterion"&gt;Gå inn i veddemål&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yetanothermathprogrammingconsultant.blogspot.com/2022/02/4-color-maps-another-model.html"&gt;Fargelegge et kart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lindeloev.github.io/tests-as-linear/"&gt;Teste en hypotese&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/PageRank"&gt;Lage en søkemotor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ethanrosenthal.com/2022/04/15/bayesian-rock-climbing/"&gt;Rangere klatreruter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Shapley_value"&gt;Dele en taxiregning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bygge en database&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Stable_marriage_problem"&gt;Arrangere ekteskap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Vickrey_auction"&gt;Designe en auksjon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jakevdp.github.io/blog/2017/12/18/simulating-chutes-and-ladders/"&gt;Simulere stigespillet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pubsonline.informs.org/doi/abs/10.1287/inte.2019.1015"&gt;Planlegge bussruter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/PageRank"&gt;Bygge en søkemotor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Regne på et boliglån&lt;/li&gt;
&lt;li&gt;&lt;a href="https://magazine.sebastianraschka.com/p/understanding-large-language-models"&gt;Forstå språkmodeller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Elo_rating_system"&gt;Rangere sjakkspillere&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bygge en atomreaktor&lt;/li&gt;
&lt;li&gt;Digital bildebehandling&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Gerrymandering"&gt;Manipulere valgkretser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Modern_portfolio_theory"&gt;Lage en aksjeportefølje&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/List_of_causes_of_death_by_rate"&gt;Undersøke dødsårsaker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bygge elektriske kretser&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Recommender_system"&gt;Anbefale filmer og serier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2020/optimizing-sets-and-reps-for-strength-training"&gt;Lage et treningsprogram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Forstå kunstig intelligens&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dx.doi.org/10.2139/ssrn.3928966"&gt;Matche spillere i dataspill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods"&gt;Løse en differensialligning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Dataclysm"&gt;Analysere dating-markedet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doi.org/10.1287/inte.2022.1132"&gt;Lage turnus for sykepleiere&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb"&gt;Designe effektive algoritmer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Factfulness:_Ten_Reasons_We%27re_Wrong_About_the_World_%E2%80%93_and_Why_Things_Are_Better_Than_You_Think"&gt;Forstå verden gjennom data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.regjeringen.no/contentassets/e9f862f850474dca89826e25e4b01314/pwc-rapport-utvikling-og-implementering-av-nytt-system-for-verdsettelse-av-fritidsboliger.pdf"&gt;Verdtsettelse av fritidsboliger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.stanford.edu/~boyd/cvxbook/"&gt;Finne minimum av en funksjon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Lage vaktplaner for et sykehus&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Category:Electoral_systems"&gt;Designe et demokratisk system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hdl.handle.net/11250/2835304"&gt;Finne korteste reisevei offshore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vedde penger på fotballkamper&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opendatastructures.org/"&gt;Designe effektive datastrukturer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pubsonline.informs.org/doi/abs/10.1287/trsc.2018.0873"&gt;Effektivisere robotiserte varehus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2020/pareto-non-dominated-front-as-a-consumer-strategy"&gt;Få mest mulig bolig for pengene&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Modellere et petroleumsreservoir&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.equinor.com/no/energi/maskin-mot-maskin"&gt;Vedlikeholde roterende maskiner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://use-the-index-luke.com/"&gt;Lagre store mengder informasjon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Lage et program som spiller sjakk&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yetanothermathprogrammingconsultant.blogspot.com/2016/12/table-assignment-for-kindergartners.html"&gt;Planlegge sitteplasser i barnehagen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doi.org/10.1287/inte.1060.0252"&gt;Forsvare infrastruktur mot terrorister&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Seam_carving"&gt;Innholdsbevisst reskalering av bilder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Forstå hvordan et aksjefond fungerer&lt;/li&gt;
&lt;li&gt;&lt;a href="https://store.steampowered.com/app/1444480/Turing_Complete/"&gt;Bygge en prosessor til en datamaskin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Lage en valgomat for kommunevalget&lt;/li&gt;
&lt;li&gt;Bygge skyskrapere som ikke kollapser&lt;/li&gt;
&lt;li&gt;&lt;a href="https://studenttorget.no/index.php?show=4524&amp;amp;expand=3797,4524&amp;amp;artikkelid=19709"&gt;Effektivisere Lånekassens bokontroller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://math.stackexchange.com/questions/74440/optimization-volume-of-a-box"&gt;Bygge en boks med størst mulig volum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2022/optimal-pupil-classroom-assignments"&gt;Sette sammen en ungdomsskoleklasse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.shifter.no/nyheter/na-kommer-ai-teamet-til-ruter-med-en-slags-ruter-gpt-for-nesten-all-kollektivtransport-i-norge/283598"&gt;Predikere hvor billettkontrollører bør stå&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Analysere data fra spørreundersøkelser&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Cost_estimation_models"&gt;Estimere kostnadene til et stort prosjekt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.stanford.edu/~boyd/papers/opt_ss_claim.html"&gt;Bestemme når man bør pensjonere seg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2022/the-2022-norwegian-football-elite-series"&gt;Predikere hvem som finner fotballkamper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.regjeringen.no/contentassets/e9f862f850474dca89826e25e4b01314/pwc-rapport-utvikling-og-implementering-av-nytt-system-for-verdsettelse-av-fritidsboliger.pdf"&gt;Verdsette fritidsboliger med maskinlæring&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2021/the-best-pokemon-party"&gt;Sette sammen det beste Pokémon-partiet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2308.01074"&gt;Logge tastetrykk ved hjelp av en mikrofon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Sette best mulig pris på en Airbnb-leilighet&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mc-stan.org/"&gt;Lage en Bayesiansk sannsynlighetsmodell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.semanticscholar.org/paper/Effect-of-Optimized-Versus-Guidelines%E2%80%90Based-on-An-Sun-Karlsson/7f19d1c6ab3ece5ea639270b574c5efd181734ef"&gt;Plassere hjertestartere på best mulig steder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Forstå &lt;span class="caps"&gt;DNA&lt;/span&gt;-sekvenser til mennesker og dyr&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2023/en-pose-twist"&gt;Finne ut om du blir snytt når du kjøper Twist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tu.no/artikler/lars-magnar-rettet-ut-kommunens-svingete-tunnelforslag-i-siste-oyeblikk-sparte-125-millioner-kroner/365455"&gt;Spare 125 millioner på å borre en rett tunnel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Programmere og studere logikken til en heis&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Monte_Carlo_method"&gt;Regne ut om det er best å eie eller leie bolig&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hdl.handle.net/1956/20441"&gt;Sette sammen forsikringer for marin shipping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20170216052444/https://e-reports-ext.llnl.gov/pdf/240921.pdf"&gt;Projisere høye dimensjoner til få dimensjoner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2020/fritt-skolevalg-sammendrag"&gt;Lage en inntaksmodell for videregående skole&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bestemme hvor mye et bakeri bør bake hver dag&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Cutting_stock_problem"&gt;Kutte tekstilruller slik at man får minst mulig svinn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Designe en kaffekopp som holder godt på varmen&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mc-stan.org/users/documentation/case-studies/golf.html"&gt;Regne ut sannsynligheten for suksessfull putt i golf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://doi.org/10.1287/mnsc.2020.3748"&gt;Designe loot-boxes i dataspill for å maksimere profitt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/articles/2021/lonn-og-kjonn-blant-utviklere"&gt;Finne ut hvilke faktorer som er assosiert med høy lønn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sortere.no/kart"&gt;Plassere returpunkter for glass- og metallembalasje i byen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/FET_(timetabling_software)"&gt;Lage timeplan for en skole for best mulig ressursutnyttelse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Teste om man klarer å smake forskjell på brus med og uten sukker&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Gibbard%E2%80%93Satterthwaite_theorem"&gt;Forstå hvorfor man ikke alltid bør stemme på politikerne man ønsker&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Thu, 17 Jul 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-07-17:/articles/2025/hva-kan-jeg-bruke-matte-til</guid><category>articles</category><category>mathematics</category></item><item><title>Who’s the best bettor?</title><link>https://tommyodland.com/articles/2025/whos-the-best-bettor</link><description>&lt;p&gt;A friend of mine is part of a group that arranges bets at their workplace.
Each person proposes bets they believe will come true.
The bets have &lt;a href="https://en.wikipedia.org/wiki/Odds#Decimal_odds"&gt;European-style odds&lt;/a&gt; &lt;span class="math"&gt;\(o\)&lt;/span&gt; and are from a betting exchange.
The money placed on each bet is always the same, so we arbitrarily take it to be one unit.
Each bet leads to an outcome &lt;span class="math"&gt;\(y = 1\)&lt;/span&gt; (win) or &lt;span class="math"&gt;\(y = 0\)&lt;/span&gt; (loss).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Given histories of bets for each person, how can we decide who the best bettor is?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For instance, given three people Bob, Alice and Mark with histories:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;person    odds (o)             outcomes (y)
bob       (2, 2, 2)            (1, 1, 1)    
alice     (3, 3)               (1, 1)    
mark      (2, 2, 2, 2, 2)      (1, 1, 1, 1, 0)  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Who is the best bettor? 
Both Bob and Alice have won all their bets, but Alice took higher risk.
Mark placed more bets that came through, but one of his bets was wrong.&lt;/p&gt;
&lt;p&gt;In this article we will define the likelihood of an outcome over a sequence of bets.
Then we create a latent variable that encodes the skill of a bettor, relative to the probability given by the odds.
Given a sequence of bets and outcomes we can then solve for this latent variable, and the best bettor is the person with the greatest value.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/best_bettor/best_bettor_players.jpg" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;&lt;/p&gt;
&lt;h2 id="the-probability-of-an-outcome"&gt;The probability of an outcome&lt;/h2&gt;
&lt;p&gt;A person performs a sequence of &lt;span class="math"&gt;\(n\)&lt;/span&gt; bets with odds &lt;span class="math"&gt;\(o_i\)&lt;/span&gt; and attains outcomes &lt;span class="math"&gt;\(y_i\)&lt;/span&gt; for each bet &lt;span class="math"&gt;\(i=1, 2, \ldots, n\)&lt;/span&gt;.
Ignoring the margin of the bookmaker, the odds can be converted to a probabilities by the equation &lt;span class="math"&gt;\(p_i = 1 / o_i\)&lt;/span&gt;.
Each bet is independent, so the probability of the joint outcome is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
P(\boldsymbol{p}, \boldsymbol{y}) = \prod_i^n p_i^{y_i} (1 - p_i)^{1 - y_i}.
\end{equation*}&lt;/div&gt;
&lt;h2 id="insight"&gt;Insight&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;When a bet will be made.&lt;/strong&gt;
Suppose a person believes the true probability of winning a bet is &lt;span class="math"&gt;\(\tilde{p}\)&lt;/span&gt;.
With European-style odds the net gain is &lt;span class="math"&gt;\((o-1)\)&lt;/span&gt; if a bet is won and &lt;span class="math"&gt;\(-1\)&lt;/span&gt; if it is lost.
A person will only bet if the expected payout is larger than zero, that is, if
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
P(\text{win}) (\text{amount won}) + P(\text{loss}) (\text{amount lost}) = \tilde{p} (o - 1) + (1 - \tilde{p}) (-1) \geq 0.
\end{equation*}&lt;/div&gt;
&lt;p&gt;
Solving for &lt;span class="math"&gt;\(\tilde{p}\)&lt;/span&gt; we learn that a person will only bet if &lt;span class="math"&gt;\(\tilde{p} \geq 1/o = p\)&lt;/span&gt;.
In other words, they will bet if their own personal probability is greater than the one deduced from the odds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Introducing the latent insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/strong&gt;
We will assume that each person has some &lt;em&gt;insight&lt;/em&gt; &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;, which is a latent (unobserved) variable representing their additional information.
We would like to infer how much insight each person has, and rank their betting ability by their insight.
One idea is to set &lt;span class="math"&gt;\(\tilde{p} = p + \theta\)&lt;/span&gt;, but we prefer &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; to be an unbounded variable and the probabilities &lt;span class="math"&gt;\(\tilde{p}\)&lt;/span&gt; to be restricted to the domain &lt;span class="math"&gt;\([0, 1]\)&lt;/span&gt;.
To accomplish this we use the logistic function (sigmoid or &lt;em&gt;expit&lt;/em&gt;) and its inverse the &lt;em&gt;logit&lt;/em&gt;.
The functions are:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\operatorname{expit}(z) = \frac{1}{1 + \exp(-z)} 
\quad \text{and} \quad
\operatorname{logit}(z) = \log\left( \frac{z}{1 - z} \right).
\quad
\end{equation*}&lt;/div&gt;
&lt;p&gt;
To ensure that &lt;span class="math"&gt;\(\tilde{p}\)&lt;/span&gt; ends up in the domain &lt;span class="math"&gt;\([0, 1]\)&lt;/span&gt; we transform the probabilities to logits on &lt;span class="math"&gt;\(\mathbb{R}\)&lt;/span&gt;, add the insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;, and transform the result back to &lt;span class="math"&gt;\([0, 1]\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
\tilde{p}(\theta, p) = \operatorname{expit}\left( \operatorname{logit}(p) + \theta \right) = \frac{p e^{\theta}}{p e^{\theta} - p + 1}
\end{equation*}&lt;/div&gt;
&lt;p&gt;Substituting &lt;span class="math"&gt;\(\tilde{p}(\theta, p)\)&lt;/span&gt; into the probability of the joint outcome, we obtain the negative log-likelihood function
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
- \log P( \boldsymbol{p}, \boldsymbol{y} \mid \theta) &amp;amp;= -
\sum_i^n  y_i \log \left( \tilde{p}_i(\theta, p_i) \right) +
(1 - y_i) \log \left( 1 -  \tilde{p}_i(\theta, p_i) \right) \\
&amp;amp;= \sum_i^n
- y_i \log{\left(\frac{p_i e^{\theta}}{p_i e^{\theta} - p_i + 1} \right)}
+ \left(y_i - 1\right) \log{\left(\frac{1 - p_i}{p_i e^{\theta} - p_i + 1} \right)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
The likelihood is the probability of observing &lt;span class="math"&gt;\((\boldsymbol{p}, \boldsymbol{y})\)&lt;/span&gt; given a value of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why a prior is needed.&lt;/strong&gt;
Suppose someone has made three bets with odds &lt;span class="math"&gt;\(\boldsymbol{o} = (2, 2, 2)\)&lt;/span&gt; and outcomes &lt;span class="math"&gt;\(\boldsymbol{y} = (1, 1, 1)\)&lt;/span&gt;.
Plotting the negative log-likelihood as a function of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; we get the following figure.
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/best_bettor/best_bettor_negative_log_likelihood.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;
Minimizing the negative log-likelihood leads to &lt;span class="math"&gt;\(\theta \to \infty\)&lt;/span&gt;.
In a sense, the person is able to claim “I’m great at betting! I knew all along that the true probability was one for all these bets!”&lt;/p&gt;
&lt;p&gt;This is analogous to estimating the probability of heads for a sequence of coin tosses as the number of heads divided by the total number of coin tosses.
If we get three heads, then the estimated probability is one, and that is unreasonable.&lt;/p&gt;
&lt;h2 id="putting-a-prior-on-insight"&gt;Putting a prior on insight&lt;/h2&gt;
&lt;p&gt;It’s far-fetched to believe that someone who wins a bet or two really has great insight.
To fix this issue of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; going to infinity, we put a prior distribution on it.
We center a normal distribution around zero, and since the standard deviation is somewhat arbitrary we choose unity.
In other words we choose &lt;span class="math"&gt;\(\theta \sim \mathcal{N}(1, 0)\)&lt;/span&gt; as the prior.
A single squared term is added to the negative log-likelihood (now a log-posterior), which becomes
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
- \log P(\theta \mid \boldsymbol{p}, \boldsymbol{y}) &amp;amp;\propto -
\sum_i^n  y_i \log \left( \tilde{p}_i(\theta, p_i) \right) +
(1 - y_i) \log \left( 1 -  \tilde{p}_i(\theta, p_i) \right) + \theta^2 \\
&amp;amp;\propto \theta^{2} - \sum_i^n \theta y_i + \log{\left(p_i e^{\theta} - p_i + 1 \right)}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
As a result, the negative log-posterior now has a finite minimizer:
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/best_bettor/best_bettor_negative_log_posterior.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;
We now have a reasonable statistical model.&lt;/p&gt;
&lt;p&gt;To summarize, we assumed that each person had some insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; that led them to believe that the true probabilities &lt;span class="math"&gt;\(\tilde{p}_i\)&lt;/span&gt; are higher than the &lt;span class="math"&gt;\(p_i\)&lt;/span&gt; deduced from the odds.
Then we compute the maximum a posteriori (&lt;span class="caps"&gt;MAP&lt;/span&gt;) estimate of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;, which is the most probable value of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; given the observed sequence of bets.
A higher value of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; means the person has insight and beats the bookmaker, &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; around zero means no additional insight is available apart from the one deduced by the odds, and a negative value of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; means the person consistently underperforms.&lt;/p&gt;
&lt;h2 id="cases"&gt;Cases&lt;/h2&gt;
&lt;p&gt;We will now go through some concrete cases and check if the model is sensible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Placing more and more bets.&lt;/strong&gt;
Suppose the winning streak is increased by one bet at a time.
The table below shows odds, outcomes and &lt;span class="caps"&gt;MAP&lt;/span&gt; estimates of the insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;case  odds (o)       outcomes (y)  insight (theta)
1     (,)            (,)            0.0000
2     (2,)           (1,)           0.2223
3     (2, 2)         (1, 1)         0.4011
4     (2, 2, 2)      (1, 1, 1)      0.5491
5     (2, 2, 2, 2)   (1, 1, 1, 1)   0.6748
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;No bets placed implies zero insight, as there is no reason to believe the bettor is any better than randomly guessing.
As the number of consecutive wins increases, the posterior mode moves away from the prior and we believe more and more that the person really has insight and outperforms the bookmaker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Equal gains.&lt;/strong&gt;
Suppose earlier wins can be used to be more money on the next bets.
Here are three bettors who all multiplied their money by &lt;span class="math"&gt;\(16\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;case  odds (o)       outcomes (y)  insight (theta)
1     (16,)          (1,)           0.4526
2     (4, 4)         (1, 1)         0.6179
3     (2, 2, 2, 2)   (1, 1, 1, 1)   0.6748
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The model assigns the most insight to the person who placed four bets.
Suppose each person has some small insight &lt;span class="math"&gt;\(\epsilon\)&lt;/span&gt;.
Then the probability &lt;span class="math"&gt;\((1/16 + \epsilon) \geq (1/2 + \epsilon)^4 = (1/2)^4 + (1/2)^3 \epsilon + \mathcal{O}(\epsilon^2)\)&lt;/span&gt; when &lt;span class="math"&gt;\(\epsilon\)&lt;/span&gt; is small.
The probability that the person who places four bets ends up with four wins is lower, and it is therefore more impressive.
In other words, the model favors winning on many smaller bets rather than one big bet, even though the final monetary gain is identical.&lt;/p&gt;
&lt;p&gt;Although the amount of money placed on each bet is the same in our setup, the result above makes even more sense if we were betting on a sequence and re-investing our wins.
A longer sequence of smaller wins is more beneficial than one large win, since we can re-invest our previous earnings along the way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A win and a loss.&lt;/strong&gt;
Now consider four bettors who all win one bet and lose one bet.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;case  odds (o)       outcomes (y)  insight (theta)
1     (2, 2)         (1, 0)         0.0
2     (3, 3)         (1, 0)         0.1358
3     (4, 4)         (1, 0)         0.2088
4     (5, 5)         (1, 0)         0.2559
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The model assigns the most insight to the person who took the most risk.
This also makes sense.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Case 1 represents winning once with odds two (doubling the money), then losing one unit of money. This bettor is back to where he started.&lt;/li&gt;
&lt;li&gt;Case 5 represents winning once with odds five (returning five times the original amount of money), then losing one unit of money. This bettor is still up by four units of money.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Some random cases.&lt;/strong&gt;
Below is a table with nine cases, sorted by the inferred insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; of each bettor.
These cases are all pretty random, and are shown so the reader can study how different odds and outcomes measure up against each other.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;case  odds (o)       outcomes (y)  insight (theta)
1     (2, 2, 2)      (0, 0, 1)     -0.182
2     (2, 2)         (1, 0)         0.0
3     (2, 2, 2)      (0, 1, 1)      0.182
4     (2, 2, 2)      (1, 0, 1)      0.182
5     (5, 5)         (1, 0)         0.2559
6     (8,)           (1,)           0.4113
7     (16,)          (1,)           0.4526
8     (3, 3)         (1, 1)         0.5386
9     (2, 2, 2)      (1, 1, 1)      0.5491
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;To determine who the best bettor is, we modeled a bettor’s insight &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; as a latent variable.
The insight is added to the probability deduced from the odds, and the best bettor is the one with the greatest insight.
In order to avoid &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; becoming very large or very small in small sequences with only wins or losses, we put a prior on it.
The skill of a bettor is the maximum a posteriori estimate of &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;There might be other interesting ways of solving this problem, but I think this approach works well.
It’s conceptually simple, all test cases look good, and it’s simple to implement and compute.
Further work that might be interesting includes: making the optimization routine faster, studying the equations in greater depth, or computing the entire posterior distribution instead of finding the value that maximizes it.&lt;/p&gt;
&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;p&gt;Here’s a Python 3.11 code snippet if you want to run the model.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# 1.26.4&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy&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="nn"&gt;sp&lt;/span&gt;  &lt;span class="c1"&gt;# 1.13.0&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;functools&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;log_posterior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;odds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Compute the log-posterior of theta, given `odds` and `y`."""&lt;/span&gt;

    &lt;span class="c1"&gt;# Convert from odds to base probabilities&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;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;odds&lt;/span&gt;
    &lt;span class="c1"&gt;# Convert to logit scale using logit(z) = log(z / (1-z))&lt;/span&gt;
    &lt;span class="n"&gt;logits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;special&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Add `theta` (insight) and convet back using expit(z) = 1/(1+exp(-z))&lt;/span&gt;
    &lt;span class="n"&gt;p_tilde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;special&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logits&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Compute the log-likelihood&lt;/span&gt;
    &lt;span class="n"&gt;log_likelihood&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p_tilde&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;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&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;p_tilde&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# A more numerically stable version as theta -&amp;gt; oo is:&lt;/span&gt;
    &lt;span class="c1"&gt;# log_likelihood = -theta * (1 - y) - np.logaddexp(&lt;/span&gt;
    &lt;span class="c1"&gt;#     np.log(p), -theta + np.log1p(-p))&lt;/span&gt;

    &lt;span class="c1"&gt;# Add prior and return the negative log-posterior&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_likelihood&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="c1"&gt;# Example case&lt;/span&gt;
&lt;span class="n"&gt;odds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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="mi"&gt;1&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Optimize the log-posterior for optimal theta&lt;/span&gt;
&lt;span class="n"&gt;fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_posterior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;odds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;odds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minimize_scalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 0.549107...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sat, 07 Jun 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-06-07:/articles/2025/whos-the-best-bettor</guid><category>articles</category><category>statistics</category></item><item><title>Fraværsgrensa</title><link>https://tommyodland.com/articles/2025/fravaersgrensa</link><description>&lt;p&gt;&lt;a href="https://www.ung.no/utdanning/vgs/2816_Reglene_for_frav%C3%A6r_p%C3%A5_videreg%C3%A5ende_skole.html"&gt;Fraværsgrensa&lt;/a&gt; i videregående skole er på 10%. 
Det betyr at dersom en elev har udokumentert fravær på 10% eller mer i et fag, så mister eleven karakter i faget.
I Reddit-tråden “&lt;a href="https://old.reddit.com/r/norge/comments/1jyw373/videreg%C3%A5ende_skole_er_no_bullshit/"&gt;Videregående skole er no bullshit&lt;/a&gt;” har en elev fått varsel fordi han eller hun var borte én dag, som utløste 3 timers fravær i kroppsøving og oversteg 5%.&lt;/p&gt;
&lt;p&gt;Kroppsøving inneholder &lt;a href="https://www.udir.no/laring-og-trivsel/lareplanverket/fag-og-timefordeling/Tidligere-rundskriv/Udir-1-2015/Vedlegg-1/3/34-Yrkesfaglige-utdanningsprogram/"&gt;56 undervisningstimer&lt;/a&gt; totalt, mens f.eks. matematikk har 140 timer.
Det betyr at fraværsgrensen har ulik effekt avhengig av hvor mange skoledager som inneholder faget:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Om kroppsøving er på 3 timer per dag, så vil 2 dager med fravær betyr at man mister karakteren, fordi &lt;span class="math"&gt;\(6 / 56 \approx 0.107 \geq 0.1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Om matematikk er på 2 timer per dag, så vil 7 dager med fravær betyr at man mister karakteren, fordi &lt;span class="math"&gt;\(14 / 140 = 0.1 \geq 0.1\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Selv om det er mye fravær som kan &lt;a href="https://www.udir.no/regelverk-og-tilsyn/skole-og-opplaring/rundskriv-om-fravarsgrensen/3.-hva-omfattes-av-fravarsgrensen/"&gt;unntas fraværsgrensen&lt;/a&gt;, er det rimelig å anta at noe udokumentert fravær vil forekomme.
Denne artikkelen svarer på spørsmålet: “gitt at udokumentert fravær inntreffer tilfeldig, hva er sannsynligheten for at en elev mister karakteren i et fag?”&lt;/p&gt;
&lt;h2 id="sannsynligheten-for-a-miste-karaktergrunnlaget-i-et-fag"&gt;Sannsynligheten for å miste karaktergrunnlaget i et fag&lt;/h2&gt;
&lt;p&gt;La oss gjøre to forenklinger før vi fortsetter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vi antar at hver dag med et fag inneholder like mange timer&lt;/strong&gt;. Dette er tilfellet om man har 3 timer gym hver onsdag. Det er ikke tilfellet om man for eksempel har 2 timer matte på mandager og 1 time   på torsdager. Denne antagelsen betyr at vi slipper å telle opp antall timer innad i hver dag og antall timer i faget totalt. Dette gjør utregningene litt enklere fordi vi slipper å forholde oss til faktiske timeplaner, som uansett vil variere fra skole til skole.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vi antar at udokumentert fravær inntreffer tilfeldig&lt;/strong&gt; med en viss sannsynlighet, og at fravær er uavhengig. Med andre ord: om en elev hadde fravær i går påvirker ikke sannsynligheten for at eleven har fravær i dag.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Figuren nedenfor viser sannsynligheten for å miste karakter et fag, som funksjon av antall dager med faget.
&lt;span class="math"&gt;\(P(\text{fravær})\)&lt;/span&gt; er sannsynligheten for fravær på en gitt dag, og utregningen er visualisert for 1%, 2% og 3%.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fraværsgrensa/sannsynlighet_miste_karakter.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 750px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Den overordnede trenden er at sannsynligheten for å miste karakter er høyere desto mindre antall dager man har med faget.
Det stemmer med de innledende observasjonene om kroppsøving og matematikk.&lt;/p&gt;
&lt;p&gt;Vi skal nå forklare knekkene i grafen: 
før man har 11 dager med et fag, vil én fraværsdag føre til at man overstiger grensa på 10%.
På 11 dager har man litt rom for fravær ettersom én dag ikke lengre gjør at man overstiger grensa.
Situasjonen blir likevel verre fra 11 dager til og med 20 dager.
I dette intervallet vil to eller flere fraværsdager utløse grensa, og &lt;strong&gt;med flere dager er det større sannsynlighet for at det skjer&lt;/strong&gt;—derfor stiger grafen.
På 21 dager har man igjen litt mer slingring, og slik fortsetter mønsteret.&lt;/p&gt;
&lt;h2 id="utregning"&gt;Utregning&lt;/h2&gt;
&lt;p&gt;La sannsynligheten for fravær være &lt;span class="math"&gt;\(p = P(\text{fravær})\)&lt;/span&gt;, la fraværsgrensa være &lt;span class="math"&gt;\(g=0.1\)&lt;/span&gt; og la antall dager med faget være &lt;span class="math"&gt;\(d\)&lt;/span&gt;.
Antall fraværsdager &lt;span class="math"&gt;\(X \sim \text{Binom}(p, d)\)&lt;/span&gt; er da en stokastisk variabel med &lt;a href="https://snl.no/binomisk_fordeling"&gt;binomisk sannsynlighetsfordeling&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sannsynligheten for at man mister karakter er &lt;span class="math"&gt;\(P(X \geq \lceil g d \rceil)\)&lt;/span&gt;, der &lt;span class="math"&gt;\(\lceil \cdot \rceil\)&lt;/span&gt; er matematisk notasjon for å runde opp, f.eks. så er &lt;span class="math"&gt;\(\lceil 1.7 \rceil = 2\)&lt;/span&gt;.
Vi regner oss frem til sannsynligheten ved hjelp av den kumulative sannsynlighetsfordelingen.&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
P(X \geq \lceil g d \rceil) = 1 - P(X &amp;lt; \lceil g d \rceil) = 1 - (P(X \leq \lceil g d \rceil) - P(X = \lceil g d \rceil)).
\end{equation*}&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Eksempel&lt;/strong&gt; &lt;br/&gt;
Om vi setter &lt;span class="math"&gt;\(p=0.02\)&lt;/span&gt; og antall dager lik &lt;span class="math"&gt;\(d=13\)&lt;/span&gt;, så er
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
P(X \geq \lceil (0.1) 13 \rceil) &amp;amp;= P(X \geq 2 ) \\
&amp;amp;= 1 - P(X &amp;lt; 2) \\
&amp;amp;= 1 - \left[ P(X = 0) + P(X = 1) \right] \\
&amp;amp;= 1 - \left[ \binom{13}{0} 0.02^{0} (1 - 0.02)^{13} + \binom{12}{1} 0.02^{1} (1 - 0.02)^{12} \right] \\
&amp;amp;= 1 - \left[ 1 (0.98)^{13} + 12 0.02 (0.98)^{12} \right] \\
&amp;amp;\approx 1 - \left[ 0.769 + 0.188 \right] \approx 0.0426455.
\end{align*}&lt;/div&gt;
&lt;h2 id="hva-br-fravrsgrensa-vre"&gt;Hva bør fraværsgrensa være?&lt;/h2&gt;
&lt;p&gt;Dette er et verdispørsmål som matematikk alene ikke kan svare på, men la oss gjøre noen antagelser og undersøke hva en rimelig fraværsgrense som funksjon av antall dager &lt;span class="math"&gt;\(d\)&lt;/span&gt; bør være:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anta at vi ønsker at &lt;span class="math"&gt;\(P(\text{ingen karakter}) \leq 0.001\)&lt;/span&gt;, gitt et normalt udokumentert fravær.&lt;/li&gt;
&lt;li&gt;Anta at noe udokumentert fravær vil forekomme, f.eks. at &lt;span class="math"&gt;\(p = 0.02\)&lt;/span&gt; eller lignende er normalnivå.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Da kan vi spørre “Hva er minste (strengeste) fraværsgrense &lt;span class="math"&gt;\(g\)&lt;/span&gt; vi kan ha som oppfyller &lt;span class="math"&gt;\(P(\text{ingen karakter}) \leq 0.001\)&lt;/span&gt;?”&lt;/p&gt;
&lt;p&gt;Svaret er gitt grafisk i figuren nedenfor, for tre ulike verdier av &lt;span class="math"&gt;\(p\)&lt;/span&gt; og ulike verdier av dager &lt;span class="math"&gt;\(d\)&lt;/span&gt; med et gitt fag.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fraværsgrensa/minste_fraværsgrense.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 750px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Å velge &lt;span class="math"&gt;\(P(\text{ingen karakter})\)&lt;/span&gt; og &lt;span class="math"&gt;\(p\)&lt;/span&gt; er en skjønnsmessig vurdering.
Uansett er konklusjonen at antall dager med faget spiller en viktig rolle.
Desto lavere (strengere) fraværsgrense, desto høyere sannsynlighet for at man har uflaks og mister karaktergrunnlaget, selv når sannsynligheten for fravær &lt;span class="math"&gt;\(p\)&lt;/span&gt; i utgangspunktet er lav.&lt;/p&gt;
&lt;p&gt;Personlig ville jeg heller straffet elever med nedsatt karakter dersom fraværet blir stort, heller enn å ta bort hele karaktergrunnlaget.
Dette gir en mer glidende overgang, i motsetning til en hard statisk grense på 10%.
Det er litt rart at politikerne ikke har valgt en annen &lt;a href="https://en.wikipedia.org/wiki/Mechanism_design"&gt;mekanisme&lt;/a&gt; enn en statisk grense på 10% for alle fagene.
Kanskje de selv hadde fravær? All matematikken vi har brukt i denne artikkelen er i hvert fall &lt;span class="caps"&gt;VGS&lt;/span&gt;-pensum.&lt;/p&gt;
&lt;h2 id="kode"&gt;Kode&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python 3.12.9,&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# numpy: 2.1.3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;scipy&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="nn"&gt;sp&lt;/span&gt;  &lt;span class="c1"&gt;# scipy: 1.15.1&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_absence&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Probability of losing your grade, given a course with `num_days` days."""&lt;/span&gt;
    &lt;span class="c1"&gt;# Limit for losing grades, in days. Hitting this means losing the grade.&lt;/span&gt;
    &lt;span class="n"&gt;days_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&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;num_days&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;limit_absence&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# Probability of absence is Binomial&lt;/span&gt;
    &lt;span class="n"&gt;distr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;binom&lt;/span&gt;&lt;span class="p"&gt;(&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;num_days&lt;/span&gt;&lt;span class="p"&gt;,&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;prob_absence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# P(X &amp;gt;= limit) = 1 - P(X &amp;lt; limit) = 1 - (CDF(limit) - P(X=limit))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;float&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;distr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days_limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;distr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pmf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days_limit&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;find_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_prob_lose_grade&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Find largest fraværsgrense (limit), such that the probability of&lt;/span&gt;
&lt;span class="sd"&gt;    losing grade is &amp;lt;= target_value."""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;objective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&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;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;max_prob_lose_grade&lt;/span&gt;

    &lt;span class="c1"&gt;# The goal is to find the largest possible limit that ensures that the&lt;/span&gt;
    &lt;span class="c1"&gt;# return value of prob_lose_grade is smaller than or equal to target_value.&lt;/span&gt;
    &lt;span class="n"&gt;optimal_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optimize&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bisect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objective&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&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;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-8&lt;/span&gt;
    &lt;span class="c1"&gt;# print(f"The absence limit should be set to {optimal_limit:.4f}")&lt;/span&gt;

    &lt;span class="n"&gt;prob_lose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optimal_limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# print(f"Then the prob of losing the grade is {prob_lose:.4f}")&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;prob_lose&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_prob_lose_grade&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optimal_limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Test case - single day&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob_lose_grade&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="n"&gt;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_absence&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Test case - two days&lt;/span&gt;
    &lt;span class="n"&gt;prob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.01&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="mf"&gt;0.01&lt;/span&gt; &lt;span class="o"&gt;*&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="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&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;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_absence&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Test case - tree days&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;
    &lt;span class="n"&gt;prob&lt;/span&gt; &lt;span class="o"&gt;=&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;p&lt;/span&gt; &lt;span class="o"&gt;*&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;3&lt;/span&gt; &lt;span class="o"&gt;*&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;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;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&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;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prob_lose_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prob_absence&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit_absence&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="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;prob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Thu, 22 May 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-05-22:/articles/2025/fravaersgrensa</guid><category>articles</category><category>statistics</category></item><item><title>How do Voting Advice Applications work?</title><link>https://tommyodland.com/articles/2025/how-do-voting-advice-applications-work</link><description>&lt;p&gt;&lt;strong&gt;This is a translation of my &lt;a href="https://tommyodland.com/articles/2025/hvordan-fungerer-en-valgomat"&gt;Norwegian article about VAAs&lt;/a&gt;.
The figures have not been translated, but are hopefully understandable from the context.&lt;/strong&gt;&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;This article presents the mathematics behind voting advice applications (VAAs).
VAAs are essentially questionnaires: a user expresses agreement or disagreement with statements about politics, and the VAAs tells the user what parties they align with.&lt;/p&gt;
&lt;p&gt;This article consists of three main parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 1: VAAs and geometry&lt;/li&gt;
&lt;li&gt;Part 2: How does a &lt;span class="caps"&gt;VAA&lt;/span&gt; explain the results?&lt;/li&gt;
&lt;li&gt;Part 3: How should a &lt;span class="caps"&gt;VAA&lt;/span&gt; present questions to the user?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The article begins at a basic mathematical level and becomes progressively more challenging.
The advantage of this approach is that there is something to learn for everyone regardless of level.
The disadvantage is that the article does not have a single clear target audience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;PS&lt;/span&gt;:&lt;/strong&gt; In 2023, &lt;a href="https://tommyodland.com/articles/2023/politikk-og-meningsrommet"&gt;I visualized the parties in the Norwegian municipal election&lt;/a&gt;.
The focus of that article was insight into party politics.
This article focuses more on the design of VAAs.&lt;/p&gt;
&lt;h1 id="part-1-vaas-and-geometry"&gt;Part 1: VAAs and geometry&lt;/h1&gt;
&lt;h2 id="opinions-and-matrices"&gt;Opinions and matrices&lt;/h2&gt;
&lt;p&gt;To create a &lt;span class="caps"&gt;VAA&lt;/span&gt;, we ask political parties to which degree they agree with various statements.
The results are quantified by translating responses like “Strongly agree” into numbers.
The answers can then be visualized in a table with one row per statement and one column per party.
Below, “Strongly agree” is translated to &lt;span class="math"&gt;\(2\)&lt;/span&gt; and “Strongly disagree” to &lt;span class="math"&gt;\(-2\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/intro_figure.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;The table forms a matrix &lt;span class="math"&gt;\(X\)&lt;/span&gt;.
The rows and columns have a geometric interpretation as &lt;em&gt;vectors&lt;/em&gt;.
A vector is a list of numbers, e.g., &lt;span class="math"&gt;\((1, 2)\)&lt;/span&gt;, and can be interpreted geometrically as an arrow or as a point.&lt;/p&gt;
&lt;h2 id="statements-and-parties"&gt;Statements and parties&lt;/h2&gt;
&lt;h3 id="statements-as-vectors"&gt;Statements as vectors&lt;/h3&gt;
&lt;p&gt;To visualize statements and parties as vectors, we must limit ourselves to two or three dimensions.
Higher dimensions cannot be visualized.&lt;/p&gt;
&lt;p&gt;Let’s study geometrically what the parties &lt;span class="caps"&gt;SV&lt;/span&gt; and Sp think about a selection of statements.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/vectors_statements.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 750px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;In the upper right of the figure, we find statements that both parties agree with.
In the lower right, we find statements that &lt;span class="caps"&gt;SV&lt;/span&gt; agrees with, but Sp disagrees with.
We can make similar observations for the other two quadrants.&lt;/p&gt;
&lt;h3 id="which-statements-are-good-and-which-are-bad"&gt;Which statements are good and which are bad?&lt;/h3&gt;
&lt;p&gt;A good statement allows us to distinguish parties from each other:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the figure above, “Build railway northward to Tromsø” (Norwegian: “Bygg jernbane nordover til Tromsø.”) is a poor statement. Regardless of whether you agree or disagree, the statement does not clarify which of the two parties you are politically closest to. Trivial statements, such as “A just society is good,” are agreed upon by all parties. Therefore, they provide no information. The same applies to trivial statements that all parties disagree with, such as “The county municipality should be abolished” (Norwegian: “Fylkeskommunen bør avskaffes.”) in the figure above.&lt;/li&gt;
&lt;li&gt;&lt;span class="dquo"&gt;“&lt;/span&gt;Drop the ferry-free E39 along the west coast” (Norwegian: “Dropp ferjefri E39 langs Vestlandet.”) is a good statement because it &lt;em&gt;discriminates&lt;/em&gt; between the parties. If you agree, you are politically closer to &lt;span class="caps"&gt;SV&lt;/span&gt;. If you disagree, you are closer to Sp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In two dimensions (two parties), statements are good if they are far from the subspace spanned by the vector &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1)\)&lt;/span&gt;, i.e., the diagonal line &lt;span class="math"&gt;\(y=x\)&lt;/span&gt;.
At the end of this article, we will generalize this idea to construct an algorithm that finds a good ordering of questions.&lt;/p&gt;
&lt;h3 id="parties-as-vectors"&gt;Parties as vectors&lt;/h3&gt;
&lt;p&gt;Above, we considered the rows (statements) in the table as vectors.
Let’s now look at the complementary vector space where the columns (parties) are vectors.
We choose two random statements: one about wolves (“Det skytes for mye ulv her i landet.” / “Too many wolves are shot in this country.”) and one about state ownership in companies (“Staten bør eie flere bedrifter.” / “The state should own more companies.”).&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/vectors_parties.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Two parties are similar if they are close to each other in the space of opinions in the figure above.
For example, we can measure distance as the distance on the horizontal axis plus the distance on the vertical axis (a mathematician might call this “Manhattan distance” or &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; norm).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parties R and &lt;span class="caps"&gt;AP&lt;/span&gt; are close to each other. The distance is &lt;span class="math"&gt;\(0 + 1 = 1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Parties R and V are far from each other. The distance is &lt;span class="math"&gt;\(3 + 4 = 7\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The distance in this space is the key to how a &lt;span class="caps"&gt;VAA&lt;/span&gt; determines which parties you most agree with when you take the test.
This is what we will cover in part 2.&lt;/p&gt;
&lt;h1 id="part-2-how-does-a-vaa-explain-the-results"&gt;Part 2: How does a &lt;span class="caps"&gt;VAA&lt;/span&gt; explain the results?&lt;/h1&gt;
&lt;h2 id="explanation-ranking-parties-by-distance"&gt;Explanation: ranking parties by distance&lt;/h2&gt;
&lt;p&gt;Above, we saw that party similarity can be quantified by looking at the distance between parties.
We visualized this in two dimensions, but the calculation is the same in any number of dimensions.&lt;/p&gt;
&lt;p&gt;Here is a matrix with the numbers from the first figure in this article:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
X =
\begin{bmatrix}
  \text{AP} &amp;amp;  \text{Sp} &amp;amp; \text{Frp} &amp;amp; \text{H}\\
  -1 &amp;amp;  1 &amp;amp; -2 &amp;amp; -1\\
  2  &amp;amp;  1 &amp;amp; -2 &amp;amp; -2\\
  -2 &amp;amp; -2 &amp;amp;  2 &amp;amp;  2\\
  1  &amp;amp; -1 &amp;amp; -2 &amp;amp; -2\\
  1  &amp;amp;  2 &amp;amp;  2 &amp;amp; -2\\
  -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; 2
\end{bmatrix}
\end{equation*}&lt;/div&gt;
&lt;p&gt;If you take the &lt;span class="caps"&gt;VAA&lt;/span&gt; and your opinions are the vector &lt;span class="math"&gt;\(\boldsymbol{y} = (-1, -2, 2, -2, 0, 0)\)&lt;/span&gt;, then your distance to &lt;span class="caps"&gt;AP&lt;/span&gt; is &lt;span class="math"&gt;\(13\)&lt;/span&gt;, to Sp &lt;span class="math"&gt;\(14\)&lt;/span&gt;, to Frp &lt;span class="math"&gt;\(5\)&lt;/span&gt;, and to H &lt;span class="math"&gt;\(4\)&lt;/span&gt;.
In other words, you are likely on the political right and should consider voting for Frp or H.&lt;/p&gt;
&lt;p&gt;The distance &lt;span class="math"&gt;\(d_j\)&lt;/span&gt; between your opinions &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; and a party &lt;span class="math"&gt;\(j\)&lt;/span&gt;’s opinions is given by the sum of the distances for each statement &lt;span class="math"&gt;\(i\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
d_j =  \sum_i | X_{ij} - y_i |
\end{equation*}&lt;/div&gt;
&lt;h3 id="distances-p-norms-and-weighting"&gt;Distances, &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norms, and weighting&lt;/h3&gt;
&lt;p&gt;We can generalize to an arbitrary &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm and add a weighting &lt;span class="math"&gt;\(w_i\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
d_j = \left( \sum_i w_i | X_{ij} - y_i |^p \right)^{(1/p)}
\end{equation*}&lt;/div&gt;
&lt;p&gt;
The weights &lt;span class="math"&gt;\(w_i\)&lt;/span&gt; are typically equal to &lt;span class="math"&gt;\(1\)&lt;/span&gt; for most statements, while &lt;span class="math"&gt;\(w_i = 2\)&lt;/span&gt; for core issues or issues that are particularly important to you.
For issues where you have no opinion, we can set &lt;span class="math"&gt;\(w_i = 0\)&lt;/span&gt;.
The value chosen for &lt;span class="math"&gt;\(p\)&lt;/span&gt; depends on how you want to measure distance.&lt;/p&gt;
&lt;p&gt;If you believe that &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; is closer to party &lt;span class="math"&gt;\(A\)&lt;/span&gt; in the example below, you should choose &lt;span class="math"&gt;\(p=2\)&lt;/span&gt;.
If you think &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; is closer to &lt;span class="math"&gt;\(B\)&lt;/span&gt;, you should choose &lt;span class="math"&gt;\(p=1\)&lt;/span&gt;.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{y} &amp;amp;= (0, 0, 0, 0) \\
A &amp;amp;= (1, 1, 1, 1) \\
B &amp;amp;= (0, 0, 0, 3) 
\end{align*}&lt;/div&gt;
&lt;p&gt;
This is a subjective assessment, and personally, I lean toward &lt;span class="math"&gt;\(p=1\)&lt;/span&gt;.
Both of &lt;span class="caps"&gt;NRK&lt;/span&gt; and &lt;span class="caps"&gt;VG&lt;/span&gt; do the same; they both use &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; in their VAAs.&lt;/p&gt;
&lt;p&gt;The calculations ultimately result in a list of parties, sorted by distance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1. H   (distance 4 )
2. Frp (distance 5 )
3. AP  (distance 13)
4. Sp  (distance 14)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Such a list, sorted by distance, is one possible way to explain the user’s results.
But it’s not the only option; the next section covers another type of explanation.&lt;/p&gt;
&lt;h2 id="explanation-your-position-as-a-combination-of-party-positions"&gt;Explanation: Your position as a combination of party positions&lt;/h2&gt;
&lt;p&gt;The parties’ vectors set directions in the space of opinions.
After taking a &lt;span class="caps"&gt;VAA&lt;/span&gt;, you also get a position in this space.
(“Ditt standpunkt” means “Your standpoint” or “Your position” in English.)&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/vectors_parties_with_user.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;In the figure above, your position is &lt;span class="math"&gt;\(\boldsymbol{y} = (1, -2)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;One way to reach your position with vector arithmetic is to take &lt;span class="caps"&gt;MDG&lt;/span&gt;’s position and add KrF’s.
This can be observed geometrically in the figure above, or algebraically:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\text{MDG} + \text{KrF} = \boldsymbol{y} \\
(2, -1) + (-1, -1) &amp;amp;= (1, -2)
\end{align}&lt;/div&gt;
&lt;h3 id="drafting-an-optimization-model"&gt;Drafting an optimization model&lt;/h3&gt;
&lt;p&gt;Here is an alternative idea to a distance-based explanation: we express your position as a combination of party positions.
We are looking for a combination of parties that describes you by taking us as close to your position as possible.
In other words, we are search for a vector &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; that solves the optimization problem
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
&amp;amp; \underset{\boldsymbol{\beta}}{\text{minimize}}   &amp;amp;&amp;amp; \| X \boldsymbol{\beta} - \boldsymbol{y}  \|.
\end{align}&lt;/div&gt;
&lt;p&gt;This model has some practical challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It allows complicated explanations in the form of long vectors &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt;.
For example, we can reach &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; through the linear combination
&lt;div class="math"&gt;\begin{align}
\text{V} + \text{R} + \text{H} + \text{Ap}  + \text{MDG} = \boldsymbol{y}.
\end{align}&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;The model allows negative directions because &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; can consist of negative numbers. This is difficult to interpret. For example, &lt;span class="math"&gt;\(-\text{R} = \boldsymbol{y}\)&lt;/span&gt; in the example above, but you naturally want to know who you agree with, rather than being told that you disagree with the party &lt;span class="math"&gt;\(\text{R}\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The model allows explanations where the sum of the parties’ contributions &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; exceeds &lt;span class="math"&gt;\(1\)&lt;/span&gt;. This is the case in &lt;span class="math"&gt;\(\text{MDG} + \text{KrF} = \boldsymbol{y}\)&lt;/span&gt; and in the long explanation above. Again, it is difficult to interpret such an answer.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these problems can be solved if we limit ourselves to &lt;em&gt;convex combinations&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="final-optimization-model"&gt;Final optimization model&lt;/h3&gt;
&lt;p&gt;We fix the problems in the above model by adding some constraints.
Rather than look for any &lt;em&gt;linear combination&lt;/em&gt; of parties, we look for the &lt;em&gt;shortest convex combination&lt;/em&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
&amp;amp; \underset{\boldsymbol{\beta}}{\text{minimize}}   
&amp;amp;&amp;amp; \| X \boldsymbol{\beta} - \boldsymbol{y}  \|
+ \epsilon \|  \boldsymbol{\beta} \|
  \\
&amp;amp; \text{subject to} &amp;amp;&amp;amp; \beta_j \geq 0 &amp;amp;&amp;amp; \\
&amp;amp;  &amp;amp;&amp;amp; \sum_j \beta_j = 1. &amp;amp;&amp;amp; 
\end{align}&lt;/div&gt;
&lt;p&gt;
The regularization term &lt;span class="math"&gt;\(\epsilon \|  \boldsymbol{\beta} \|\)&lt;/span&gt; means that we prefer simple (short) explanations.
The two constraints on &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; are the definition of a convex combination, and ensure that we avoid negative directions and sums that exceed &lt;span class="math"&gt;\(1\)&lt;/span&gt;.
The sum &lt;span class="math"&gt;\(\sum_j \beta_j\)&lt;/span&gt; must equal &lt;span class="math"&gt;\(1\)&lt;/span&gt;, rather than be less than &lt;span class="math"&gt;\(1\)&lt;/span&gt;, because if &lt;span class="math"&gt;\(\boldsymbol{y} = (0, 1)\)&lt;/span&gt;, &lt;span class="math"&gt;\(A = (0, 1)\)&lt;/span&gt;, and &lt;span class="math"&gt;\(B = (0, 2)\)&lt;/span&gt;, we expect &lt;span class="math"&gt;\(\boldsymbol{\beta} = (1, 0)\)&lt;/span&gt; as the answer, not &lt;span class="math"&gt;\(\boldsymbol{\beta} = (0, 0.5)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;With the &lt;span class="math"&gt;\(2\)&lt;/span&gt;-norm on both terms in the objective function, the solution to the optimization problem becomes:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
0.69 \, \text{V} + 0.23 \, \text{H} + 0.08 \, \text{Frp} = \boldsymbol{y}.
\end{align}&lt;/div&gt;
&lt;p&gt;
If you go back to the figure and compare the geometry with this solution, you’ll see that the solution makes sense.
That said, there are often no single correct approach in mathematical modeling, and other variations of such a model might be appropriate.&lt;/p&gt;
&lt;h3 id="the-optimization-model-on-a-larger-example"&gt;The optimization model on a larger example&lt;/h3&gt;
&lt;p&gt;Let’s run the optimization model on the same example as before:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
[X \mid \boldsymbol{y}] =
\begin{bmatrix}
  \text{AP} &amp;amp;  \text{Sp} &amp;amp; \text{Frp} &amp;amp; \text{H} &amp;amp; \boldsymbol{y} \\
  -1 &amp;amp;  1 &amp;amp; -2 &amp;amp; -1 &amp;amp; -1\\
  2  &amp;amp;  1 &amp;amp; -2 &amp;amp; -2 &amp;amp; -2\\
  -2 &amp;amp; -2 &amp;amp;  2 &amp;amp;  2 &amp;amp; 2\\
  1  &amp;amp; -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; -2 \\
  1  &amp;amp;  2 &amp;amp;  2 &amp;amp; -2 &amp;amp; 0\\
  -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; 2 &amp;amp; 0
\end{bmatrix} 
\end{align*}&lt;/div&gt;
&lt;p&gt;The answer is given below and corresponds well with the ranking we obtained earlier.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1. H   (51%)
2. Frp (45%)
4. Sp  (4%)
3. AP  (0%)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="the-difference-between-distance-based-and-combination-based-explanation"&gt;The difference between distance-based and combination-based explanation&lt;/h3&gt;
&lt;p&gt;We have seen two ways to explain a user’s results in a &lt;span class="caps"&gt;VAA&lt;/span&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Distance-based explanation&lt;/strong&gt; - ranks parties by distance from the user&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Combination-based explanation&lt;/strong&gt; - the user is expressed as the nearest convex combination of parties&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The difference between the explanations is illustrated in the figure below.
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/opt_model_vs_distance.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distance-based explanation says that &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; is closest to &lt;span class="math"&gt;\(B\)&lt;/span&gt;, then &lt;span class="math"&gt;\(A\)&lt;/span&gt;, and finally &lt;span class="math"&gt;\(C\)&lt;/span&gt;. Distance-based explanation is &lt;em&gt;mutually independent&lt;/em&gt;; the distance to &lt;span class="math"&gt;\(B\)&lt;/span&gt; does not affect the distance to &lt;span class="math"&gt;\(A\)&lt;/span&gt;. If &lt;span class="math"&gt;\(B\)&lt;/span&gt; had disappeared, the distance to &lt;span class="math"&gt;\(A\)&lt;/span&gt; would remain unchanged.&lt;/li&gt;
&lt;li&gt;Combination-based explanation expresses the position &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; as a convex combination. The answer in the figure above becomes &lt;span class="math"&gt;\(\boldsymbol{\beta} = (\beta_A, \beta_B, \beta_C) = (0, 0.7, 0.3)\)&lt;/span&gt;. The optimization model’s explanation is &lt;em&gt;mutually dependent&lt;/em&gt;. If &lt;span class="math"&gt;\(B\)&lt;/span&gt; had disappeared, the explanation would have relied on &lt;span class="math"&gt;\(A\)&lt;/span&gt;, but when &lt;span class="math"&gt;\(B\)&lt;/span&gt; exists, &lt;span class="math"&gt;\(A\)&lt;/span&gt; is not needed at all to explain &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The figure below shows an example where the explanations become completely different.
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/opt_model_vs_distance_different_result.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Measured by distance, &lt;span class="math"&gt;\(C\)&lt;/span&gt; is closer to &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; than both &lt;span class="math"&gt;\(A\)&lt;/span&gt; and &lt;span class="math"&gt;\(B\)&lt;/span&gt;. Yet &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; is explained using &lt;span class="math"&gt;\(\boldsymbol{\beta} = (\beta_A, \beta_B, \beta_C) = (0.5, 0.5, 0)\)&lt;/span&gt; without using &lt;span class="math"&gt;\(C\)&lt;/span&gt;.
Shortest distance tells you which single party is closest.
The optimization model tells you which coalition of parties is closest to you.&lt;/p&gt;
&lt;h1 id="part-3-how-should-a-vaa-present-questions-to-the-user"&gt;Part 3: How should a &lt;span class="caps"&gt;VAA&lt;/span&gt; present questions to the user?&lt;/h1&gt;
&lt;p&gt;In this section, we present an algorithm that selects statements that discriminate well between parties.
There may be two reasons for wanting this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;We want to use a selection of statements.&lt;/strong&gt; If parties have answered many questions, we may want to limit ourselves to a smaller selection in the &lt;span class="caps"&gt;VAA&lt;/span&gt;. How can we choose a small subset of good statements that differentiate between parties?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Early termination.&lt;/strong&gt; We want the user to be able to end the &lt;span class="caps"&gt;VAA&lt;/span&gt; after any number of questions. How do we ensure that the user has been presented with a diverse set of statements even if they do not answer every question?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To motivate the algorithm, we start by looking at the same example as earlier in the article.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/vectors_statements_with_trivial_direction.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;If we ask about a railway to Tromsø or whether the county municipality should be abolished, we gather no information because the user’s answer does not discriminate between parties.
The subspace spanned by the vector &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1)\)&lt;/span&gt; consists of such &lt;em&gt;trivial statements&lt;/em&gt;.
The best statement to present to the user is therefore “Drop the ferry-free E39 along the west coast,” since this statement lies furthest from &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e} \} )\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="algorithm-for-selecting-statements-in-sequence"&gt;Algorithm for selecting statements in sequence&lt;/h2&gt;
&lt;p&gt;Let’s generalize from two dimensions to an arbitrary number of dimensions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start with the trivial subspace spanned by the statement &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1, \ldots, 1)\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The first statement selected is the one that is furthest from &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e} \} )\)&lt;/span&gt;. This is &lt;span class="math"&gt;\(\boldsymbol{v}_1 = \arg \max_j \| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2\)&lt;/span&gt;, where &lt;span class="math"&gt;\(P_S\)&lt;/span&gt; is a projection matrix that projects a vector onto &lt;span class="math"&gt;\(S\)&lt;/span&gt;. The formula for such a projection matrix is &lt;span class="math"&gt;\(P_S = S S^{+} = S (S^T S)^{-1} S^T\)&lt;/span&gt;, where the columns of the matrix &lt;span class="math"&gt;\(S\)&lt;/span&gt; are the vectors that span the space we want to project onto (here just &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;The next statement selected should be far from both the trivial subspace &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt; and far from the previously selected statement &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt;. So we choose the statement that is furthest from &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e}, \boldsymbol{v}_1 \} )\)&lt;/span&gt;. In mathematical notation, this becomes &lt;span class="math"&gt;\(\boldsymbol{v}_2 = \arg \max_j \| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2\)&lt;/span&gt;, where &lt;span class="math"&gt;\(S\)&lt;/span&gt; in this iteration is a matrix with columns &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;We continue like this, selecting statements that are furthest from those we have already chosen. If &lt;span class="math"&gt;\(\operatorname{rank} \operatorname{span} (\{ \boldsymbol{e}, \boldsymbol{v}_1, \ldots, \boldsymbol{v}_n \} )\)&lt;/span&gt; becomes equal to the dimensionality of the space (number of parties), then all remaining statements are in the space. Then the distance &lt;span class="math"&gt;\(\| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2 \approx 0\)&lt;/span&gt; for all remaining statements &lt;span class="math"&gt;\(\boldsymbol{v}_j\)&lt;/span&gt;. The solution is to treat the selected statements as a &lt;span class="caps"&gt;FIFO&lt;/span&gt; queue: we remove the first selected statement &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt; and try again. Then the next statement we select is always far from the most recently selected statements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python code for the algorithm is included at the end of the article.
The figure below illustrates the concept in three dimensions.
The next statement selected is always the one that is furthest from the space spanned by the trivial direction and previously selected statements.
(In english, the first figure says “Start with (1, 1, 1)”, the next says “Choose first statement” and the third says “Choose second statement”.)&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/pivoted_QR_visualization.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 99%;"/&gt;&lt;/p&gt;
&lt;h2 id="results-bad-statements"&gt;Results - bad statements&lt;/h2&gt;
&lt;p&gt;Let’s look at an example with statements that are similar to each other.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/statements_bad_order.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;The first two statements are very similar.
The third statement about Erna is on the opposite side of the vector space but still does not provide much new information because it is close to the space spanned by the first two statements.
In other words: the question about Erna thus reveals little new information, despite the statement being on the opposite side of the vector space.&lt;/p&gt;
&lt;h2 id="results-good-statements"&gt;Results - good statements&lt;/h2&gt;
&lt;p&gt;Here is a selection of statements that are different from each other, identified by the algorithm above.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/statements_orthogonal_order.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;The first statement is far from trivial because half of the parties disagree and the other half agree.
The second statement is answered differently by six parties and differs from both the trivial statement and the first statement.
The algorithm continues to select statements that are consistently different from what the &lt;span class="caps"&gt;VAA&lt;/span&gt; user has already been presented with.&lt;/p&gt;
&lt;h1 id="summary-and-references"&gt;Summary and references&lt;/h1&gt;
&lt;p&gt;Voting advice applications quantify political opinions.
Party responses can be represented as a matrix, where the rows (statements) and columns (parties) can be interpreted as vectors.
Such a geometric interpretation gives us valuable insight: we can define what it means for a person to be close to a party, and we can formalize what it means for a statement to discriminate between parties.&lt;/p&gt;
&lt;p&gt;The idea of viewing parties as vectors and ranking users by proximity is obvious, and &lt;a href="https://nrkbeta.no/2019/07/03/slik-snekret-vi-en-valgomat-for-hele-landet/"&gt;for the 2019 municipal election, &lt;span class="caps"&gt;NRK&lt;/span&gt; used&lt;/a&gt; &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; as a metric.
(The fact that NRKbeta’s article in the link above describes middle school mathematics in phrases like “explain the calculation to me who doesn’t have math anxiety” and “I get a headache!” is sad.)
The idea of describing a user as a convex combination of parties is my own.
In practice, it is more difficult to implement in code and more challenging for an average user to understand.
Simple algorithms have inherent value, and distance calculation provides a simple and understandable explanation.&lt;/p&gt;
&lt;p&gt;The idea of viewing statements as vectors and selecting statements that are far from each other is my own.
The inspiration comes from &lt;a href="https://en.wikipedia.org/wiki/QR_decomposition"&gt;&lt;span class="caps"&gt;QR&lt;/span&gt; decomposition with pivoting&lt;/a&gt;, which is an algorithm for orthogonalizing a matrix.
The pivot columns in &lt;span class="caps"&gt;QR&lt;/span&gt; can be seen as a greedy approximation of D-optimality in &lt;a href="https://en.wikipedia.org/wiki/Optimal_experimental_design"&gt;optimal experimental design&lt;/a&gt;, which maximizes the volume of a high-dimensional parallelepiped.
The differences between my algorithm and &lt;span class="caps"&gt;QR&lt;/span&gt; decomposition are that (1) we always stay away from the trivial statement &lt;span class="math"&gt;\(\boldsymbol{e} = \boldsymbol{1}\)&lt;/span&gt; and that (2) we use a &lt;span class="caps"&gt;FIFO&lt;/span&gt; queue.
Again I believe simplicitiy has inherent value: I like that this algorithm does &lt;em&gt;not&lt;/em&gt; adapt the sequence of questions to what the user responded, since I do not like the idea of a personalized &lt;span class="caps"&gt;VAA&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This article focused mostly on mathematics, but a &lt;span class="caps"&gt;VAA&lt;/span&gt; must of course contain politically relevant statements, good graphic design, potentially the ability to skip statements or weight statements up, subjective analyses of the results, and so on.&lt;/p&gt;
&lt;h2 id="code"&gt;Code&lt;/h2&gt;
&lt;p&gt;Here is a simple implementation of the algorithm that finds statements that are far from each other.
I have not emphasized runtime, numerical stability, caching of calculations, etc.
The code is certainly good enough to showcase the idea, but for larger datasets, it should be rewritten.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;  &lt;span class="c1"&gt;# Python 3.12&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# NumPy 2.1.3&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;away_from_last_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&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="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Yields the column indices of A that are as far away from a continually&lt;/span&gt;
&lt;span class="sd"&gt;    updated subspace spanned by (1, 1, 1, ...) and a First-In-First-Out (FIFO)&lt;/span&gt;
&lt;span class="sd"&gt;    queue of length k. When a column index is yielded, it is added to the queue.&lt;/span&gt;

&lt;span class="sd"&gt;    Parameters&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    A : np.ndarray&lt;/span&gt;
&lt;span class="sd"&gt;        A matrix (2D array) with columns [v1 | v2 | ... ].&lt;/span&gt;
&lt;span class="sd"&gt;    k : int, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Numbers of elements to keep in the FIFO queue. None means no limit.&lt;/span&gt;
&lt;span class="sd"&gt;    verbose : bool, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Whether to print information. The default is False.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; A = np.array([[3, 0, 0], [0, 2, 0], [0, 0, 1], [2.5, 0, 0]]).T&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=None))&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 1, 3, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=0)) #  Distance from span({(1, 1, 1)}) only&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 3, 1, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=1))&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 1, 3, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;
    &lt;span class="n"&gt;subspace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="n"&gt;maxlen&lt;/span&gt;&lt;span class="o"&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;# Vector in FIFO subspace&lt;/span&gt;
    &lt;span class="n"&gt;ones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&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;shape&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="c1"&gt;# Vector of ones, always in subspace&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;enumerate&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;T&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Remaining column indices of A&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tuple_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tuple_&lt;/span&gt;
        &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;  &lt;span class="c1"&gt;# Full space as col. mat.&lt;/span&gt;
        &lt;span class="n"&gt;P_S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pinv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Projection matrix onto span(S)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P_S&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Distance between v and span(S)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;remaining&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;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;inds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Col idx of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; remaining vectors: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;inds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Vectors in subspace (excl. ones): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Get idx of col vector furthest from S = [ones | subspace]&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;maxdist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxdist&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Distance was 0. Popping from subspace."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Col idx &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; furthest from subspace. Dist: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;maxdist&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.6f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 07 Apr 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-04-07:/articles/2025/how-do-voting-advice-applications-work</guid><category>articles</category><category>mathematics</category></item><item><title>Hvordan fungerer en valgomat?</title><link>https://tommyodland.com/articles/2025/hvordan-fungerer-en-valgomat</link><description>&lt;p&gt;Denne artikkelen presenterer matematikken bak valgomater. 
Den består av tre hoveddeler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Del 1: Valgomater og geometri&lt;/li&gt;
&lt;li&gt;Del 2: Hvordan forklarer en valgomat resultatene?&lt;/li&gt;
&lt;li&gt;Del 3: Hvordan bør en valgomat presentere spørsmål for brukeren?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Artikkelen begynner på lavt matematisk nivå og blir progressivt mer krevende.
En fordel med dette er at det er noe å lære for alle, uansett nivå.
Ulempen er at artikkelen ikke har én tydelig målgruppe.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;PS&lt;/span&gt;:&lt;/strong&gt; I 2023 &lt;a href="https://tommyodland.com/articles/2023/politikk-og-meningsrommet"&gt;visualiserte jeg partiene i kommunevalget&lt;/a&gt;. 
Fokuset i den artikkelen var innsikt i partienes politikk. 
Denne artikkelen retter seg mer mot design av valgomater.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;span class="caps"&gt;PSS&lt;/span&gt;:&lt;/strong&gt; Idéene i denne artikkelen er implementert i min egen &lt;a href="https://tommyodland.com/tools/valgomat2025.html"&gt;Valgomat for Stortingsvalget 2025&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="del-1-valgomater-og-geometri"&gt;Del 1: Valgomater og geometri&lt;/h1&gt;
&lt;h2 id="meninger-og-matriser"&gt;Meninger og matriser&lt;/h2&gt;
&lt;p&gt;For å lage en valgomat spør man politiske partier hvor enige de er i ulike påstander.
Resultatet kvantifiseres ved at svar som “Helt enig!” blir oversatt til tall.
Svarene kan deretter visualiseres i en tabell med en rad per påstand og en kolonne per parti.
Nedenfor er “Helt enig” oversatt til &lt;span class="math"&gt;\(2\)&lt;/span&gt; og “Helt uenig” oversatt til &lt;span class="math"&gt;\(-2\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/intro_figure.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Tabellen utgjør en matrise &lt;span class="math"&gt;\(X\)&lt;/span&gt;.
Radene og kolonnene har en geometrisk tolkning som &lt;em&gt;vektorer&lt;/em&gt;.
En vektor er en liste med tall, e.g. &lt;span class="math"&gt;\((1, 2)\)&lt;/span&gt;, og kan tolkes geometrisk som en pil eller som et punkt.&lt;/p&gt;
&lt;h2 id="pastander-og-partier"&gt;Påstander og partier&lt;/h2&gt;
&lt;h3 id="pastander-som-vektorer"&gt;Påstander som vektorer&lt;/h3&gt;
&lt;p&gt;For å visualisere påstander og partier som vektorer må vi holde oss til to eller tre dimensjoner.
Høyere dimensjoner lar seg ikke visualisere.&lt;/p&gt;
&lt;p&gt;La oss studere geometrisk hva partiene &lt;span class="caps"&gt;SV&lt;/span&gt; og Sp mener om et utvalg påstander.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/vectors_statements.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 750px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Oppe til høyre i figuren finner vi påstander som begge partiene er enige i.
Nede til høyre finner vi påstander som &lt;span class="caps"&gt;SV&lt;/span&gt; er enige i, men som Sp er uenige i.
Vi kan gjøre tilsvarende observasjoner for de to andre kvadrantene.&lt;/p&gt;
&lt;h3 id="hvilke-pastander-er-gode-og-darlige"&gt;Hvilke påstander er gode og dårlige?&lt;/h3&gt;
&lt;p&gt;En god påstand lar oss skille partiene fra hverandre:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I figuren ovenfor er “Bygg jernbane nordover til Tromsø” en dårlig påstand. Uansett om du er enig eller uenig, avklarer ikke påstanden hvilket av de to partiene du er nærmest politisk. Trivielle påstander, som “Et rettferdig samfunn er bra”, er alle partiene enige i. Derfor gir de heller ingen informasjon. Det samme gjelder trivielle påstander som alle partiene er uenige i, for eksempel “Fylkeskommunen bør avskaffes” i figuren ovenfor. &lt;/li&gt;
&lt;li&gt;&lt;span class="dquo"&gt;“&lt;/span&gt;Dropp ferjefri E39 langs Vestlandet” er en god påstand fordi den &lt;em&gt;diskriminerer&lt;/em&gt; mellom partiene. Om du er enig ligger du nærmere &lt;span class="caps"&gt;SV&lt;/span&gt; politisk. Er du derimot uenig ligger du nær Sp.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I to dimensjoner (to partier) er påstander gode dersom de ligger langt fra underrommet som spennes av vektoren &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1)\)&lt;/span&gt;, altså den diagonale linja &lt;span class="math"&gt;\(y=x\)&lt;/span&gt;.
På slutten av denne artikkelen skal vi generalisere denne idéen for å konstruere en algoritme som finner en god rekkefølge å stille spørsmål på.&lt;/p&gt;
&lt;h3 id="partier-som-vektorer"&gt;Partier som vektorer&lt;/h3&gt;
&lt;p&gt;Ovenfor betraktet vi radene (påstandene) i tabellen som vektorer.
La oss nå se på det komplementære vektorrommet der kolonnene (partiene) er vektorer.
Vi velger to tilfeldige påstander: en om ulv og en om statens eierskap i bedrifter.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/vectors_parties.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;To partier ligner hverandre dersom de ligger nær hverandre i meningsrommet.
Vi kan for eksempel måle avstand som distanse på den horisontale aksen pluss distanse på den vertikale aksen.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Partiene R og &lt;span class="caps"&gt;AP&lt;/span&gt; er nær hverandre. Distansen er &lt;span class="math"&gt;\(0 + 1 = 1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Partiene R og V er langt fra hverandre. Distansen er &lt;span class="math"&gt;\(3 + 4 = 7\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Distansen i dette rommet er nøkkelen til hvordan en valgomat avgjør hvilke partier du er mest enig med når du tar valgomaten.
Det er dette vi nå skal ta for oss i del 2.&lt;/p&gt;
&lt;h1 id="del-2-hvordan-forklarer-en-valgomat-resultatene"&gt;Del 2: Hvordan forklarer en valgomat resultatene?&lt;/h1&gt;
&lt;h2 id="forklaring-rangering-av-partiene-etter-avstand"&gt;Forklaring: rangering av partiene etter avstand&lt;/h2&gt;
&lt;p&gt;Ovenfor så vi at partienes likhet kan kvantifiseres ved å se på avstanden mellom partiene.
Vi visualiserte dette i to dimensjoner, men beregningen er den samme i et vilkårlig antall dimensjoner.&lt;/p&gt;
&lt;p&gt;Her er en matrise med tallene fra den første figuren i denne artikkelen:&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
X =
\begin{bmatrix}
  \text{AP} &amp;amp;  \text{Sp} &amp;amp; \text{Frp} &amp;amp; \text{H}\\
  -1 &amp;amp;  1 &amp;amp; -2 &amp;amp; -1\\
  2  &amp;amp;  1 &amp;amp; -2 &amp;amp; -2\\
  -2 &amp;amp; -2 &amp;amp;  2 &amp;amp;  2\\
  1  &amp;amp; -1 &amp;amp; -2 &amp;amp; -2\\
  1  &amp;amp;  2 &amp;amp;  2 &amp;amp; -2\\
  -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; 2
\end{bmatrix}
\end{equation*}&lt;/div&gt;
&lt;p&gt;Hvis du tar valgomaten og dine meninger er vektoren &lt;span class="math"&gt;\(\boldsymbol{y} = (-1, -2, 2, -2, 0, 0)\)&lt;/span&gt;, så blir din avstand til &lt;span class="caps"&gt;AP&lt;/span&gt; &lt;span class="math"&gt;\(13\)&lt;/span&gt;, Sp &lt;span class="math"&gt;\(14\)&lt;/span&gt;, Frp &lt;span class="math"&gt;\(5\)&lt;/span&gt; og H &lt;span class="math"&gt;\(4\)&lt;/span&gt;.
Du ligger med andre ord sannsynligvis på høyresiden politisk, og bør vurdere å stemme Frp eller H.&lt;/p&gt;
&lt;p&gt;Distansen &lt;span class="math"&gt;\(d_j\)&lt;/span&gt; mellom dine meninger &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; og et parti &lt;span class="math"&gt;\(j\)&lt;/span&gt; sine meninger er gitt av summen av avstandene mellom hver påstand &lt;span class="math"&gt;\(i\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
d_j =  \sum_i | X_{ij} - y_i |
\end{equation*}&lt;/div&gt;
&lt;h3 id="avstander-p-normer-og-vekting"&gt;Avstander, &lt;span class="math"&gt;\(p\)&lt;/span&gt;-normer og vekting&lt;/h3&gt;
&lt;p&gt;Vi kan generalisere til en vilkårlig &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm og legge til en vekting &lt;span class="math"&gt;\(w_i\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
d_j = \left( \sum_i w_i | X_{ij} - y_i |^p \right)^{(1/p)}
\end{equation*}&lt;/div&gt;
&lt;p&gt;
Vektene &lt;span class="math"&gt;\(w_i\)&lt;/span&gt; er gjerne lik &lt;span class="math"&gt;\(1\)&lt;/span&gt; for de fleste påstander, mens &lt;span class="math"&gt;\(w_i = 2\)&lt;/span&gt; for kjernesaker eller saker som er spesielt viktige for deg.
For saker der du ikke har noen mening kan vi sette &lt;span class="math"&gt;\(w_i = 0\)&lt;/span&gt;.
Verdien som velges for &lt;span class="math"&gt;\(p\)&lt;/span&gt; er avhengig av hvordan man vil måle distanse.&lt;/p&gt;
&lt;p&gt;Dersom du mener at &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; er nærmere partiet &lt;span class="math"&gt;\(A\)&lt;/span&gt; i eksempelet nedenfor bør du velge &lt;span class="math"&gt;\(p=2\)&lt;/span&gt;.
Mener du derimot at &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; er nærmere &lt;span class="math"&gt;\(B\)&lt;/span&gt; bør du velge &lt;span class="math"&gt;\(p=1\)&lt;/span&gt;.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\boldsymbol{y} &amp;amp;= (0, 0, 0, 0) \\
A &amp;amp;= (1, 1, 1, 1) \\
B &amp;amp;= (0, 0, 0, 3) 
\end{align*}&lt;/div&gt;
&lt;p&gt;
Dette er en subjektiv vurdering, og personlig lener jeg mot &lt;span class="math"&gt;\(p=1\)&lt;/span&gt;.
Det samme gjør både &lt;span class="caps"&gt;NRK&lt;/span&gt; og &lt;span class="caps"&gt;VG&lt;/span&gt;; begge bruker &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; i sine valgomater.&lt;/p&gt;
&lt;p&gt;Beregningene resulterer til slutt i en liste av partier, sortert etter distanse:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1. H   (avstand 4 )
2. Frp (avstand 5 )
3. AP  (avstand 13)
4. Sp  (avstand 14)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;En slik liste, sortert etter avstand, er én mulig måte å forklare brukerens resultater på.
Men det er ikke den eneste; neste seksjon tar for seg en annen type forklaring.&lt;/p&gt;
&lt;h2 id="forklaring-ditt-standpunkt-som-en-kombinasjon-av-partienes-standpunkter"&gt;Forklaring: Ditt standpunkt som en kombinasjon av partienes standpunkter&lt;/h2&gt;
&lt;p&gt;Partienes vektorer setter retning i rommet av meninger.
Etter å ha tatt en valgomat får du også en plassering i dette rommet.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/vectors_parties_with_user.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;I figuren ovenfor er ditt standpunkt &lt;span class="math"&gt;\(\boldsymbol{y} = (1, -2)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Én måte å nå ditt standpunkt på med vektorregning er å ta MDGs standpunkt og legge på KrFs.
Dette kan observeres geometrisk i figuren ovenfor, eller algebraisk:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\text{MDG} + \text{KrF} = \boldsymbol{y} \\
(2, -1) + (-1, -1) &amp;amp;= (1, -2)
\end{align}&lt;/div&gt;
&lt;h3 id="utkast-til-optimeringsmodell"&gt;Utkast til optimeringsmodell&lt;/h3&gt;
&lt;p&gt;Her er en alternativ idé til distansebasert forklaring: vi uttrykker ditt standpunkt som en kombinasjon av partienes standpunkter.
Vi leter da etter en kombinasjon av partier som beskriver deg ved at de tar oss så nært ditt standpunkt som mulig.
Med andre ord er vi på jakt etter en vektor &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; som løser optimeringsproblemet
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
&amp;amp; \underset{\boldsymbol{\beta}}{\text{minimize}}   &amp;amp;&amp;amp; \| X \boldsymbol{\beta} - \boldsymbol{y}  \|.
\end{align}&lt;/div&gt;
&lt;p&gt;Denne modellen har noen praktiske utfordringer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Den tillater kompliserte forklaringer i form av lange vektorer &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt;.
For eksempel kan vi nå frem til &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; ved den lineære kombinasjonen
&lt;div class="math"&gt;\begin{align}
\text{V} + \text{R} + \text{H} + \text{Ap}  + \text{MDG} = \boldsymbol{y}.
\end{align}&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Modellen tillater negative retninger fordi &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; kan bestå av negative tall. Dette er vanskelig å tolke. For eksempel er &lt;span class="math"&gt;\(-R = \boldsymbol{y}\)&lt;/span&gt; i eksempelet ovenfor, men du ønsker naturligvis å vite hvem du er enig med, heller enn å få vite at du er uenig med Rødt.&lt;/li&gt;
&lt;li&gt;Modellen tillater forklaringer der summen av partienes bidrag &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; overskrider &lt;span class="math"&gt;\(1\)&lt;/span&gt;. Dette er tilfellet i &lt;span class="math"&gt;\(\text{MDG} + \text{KrF} = \boldsymbol{y}\)&lt;/span&gt; og i den lange forklaringen ovenfor. Igjen er det vanskelig å tolke et slikt svar.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alle disse problemene kan løses om vi begrenser oss til &lt;em&gt;konvekse kombinasjoner&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="endelig-optimeringsmodell"&gt;Endelig optimeringsmodell&lt;/h3&gt;
&lt;p&gt;Vi fikser problemene til modellen ovenfor ved å utvide den.
Heller enn å leter etter en hvilken som helst lineær kombinasjon av partier, leter vi etter den korteste konvekse kombinasjonen:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
&amp;amp; \underset{\boldsymbol{\beta}}{\text{minimize}}   
&amp;amp;&amp;amp; \| X \boldsymbol{\beta} - \boldsymbol{y}  \|
+ \alpha \|  \boldsymbol{\beta} \|
  \\
&amp;amp; \text{subject to} &amp;amp;&amp;amp; \beta_j \geq 0 &amp;amp;&amp;amp; \\
&amp;amp;  &amp;amp;&amp;amp; \sum_j \beta_j = 1. &amp;amp;&amp;amp; 
\end{align}&lt;/div&gt;
&lt;p&gt;
Begge noremen ovenfor er &lt;span class="math"&gt;\(2\)&lt;/span&gt;-normer.
Setter vi &lt;span class="math"&gt;\(\alpha = \epsilon\)&lt;/span&gt; (et lite positivt tall) i regulariseringsleddet, så vil modellen foretrekke løsninger der elementene i &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; jevnt over er små. Dersom &lt;span class="math"&gt;\(\alpha \to \infty\)&lt;/span&gt; så velges &lt;span class="math"&gt;\(\boldsymbol{\beta} \propto \boldsymbol{1}\)&lt;/span&gt;, dersom &lt;span class="math"&gt;\(\alpha = -\epsilon\)&lt;/span&gt; vil modellen foretrekke løsninger som bruker så få partier som mulig, og dersom &lt;span class="math"&gt;\(\alpha \to -\infty\)&lt;/span&gt; er vi tilbake til en typisk valgomat som velger ett parti.
Merk at når &lt;span class="math"&gt;\(\alpha &amp;lt; 0\)&lt;/span&gt; så er ikke problemet konvekst.
Vi setter &lt;span class="math"&gt;\(\alpha = \epsilon\)&lt;/span&gt; i resten av artikkelen.&lt;/p&gt;
&lt;p&gt;De to bibetingelsene på &lt;span class="math"&gt;\(\boldsymbol{\beta}\)&lt;/span&gt; er definisjonen på en konveks kombinasjon, og gjør at vi unngår negative retninger og summer som overskrider &lt;span class="math"&gt;\(1\)&lt;/span&gt;. Summen &lt;span class="math"&gt;\(\sum_j \beta_j\)&lt;/span&gt; må være lik &lt;span class="math"&gt;\(1\)&lt;/span&gt;, heller enn mindre enn &lt;span class="math"&gt;\(1\)&lt;/span&gt;, fordi dersom &lt;span class="math"&gt;\(\boldsymbol{y} = (0, 1)\)&lt;/span&gt;, &lt;span class="math"&gt;\(A = (0, 1)\)&lt;/span&gt; og &lt;span class="math"&gt;\(B = (0, 2)\)&lt;/span&gt; så forventer vi &lt;span class="math"&gt;\(\boldsymbol{\beta} = (1, 0)\)&lt;/span&gt; som svar, ikke &lt;span class="math"&gt;\(\boldsymbol{\beta} = (0, 0.5)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Med &lt;span class="math"&gt;\(2\)&lt;/span&gt;-norm på begge leddene i objektivfunksjonen blir løsningen på optimeringsproblemet:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
0.69 \, \text{V} + 0.23 \, \text{H} + 0.08 \, \text{Frp} = \boldsymbol{y}.
\end{align}&lt;/div&gt;
&lt;p&gt;
Om du går tilbake til figuren og sammenligner geometrien med denne løsningen, ser du at løsningen er fornuftig.
Når det er sagt er det ingen absolutt fasit i matematisk modellering og andre variasjoner av en slik modell kan være hensiktsmessige.&lt;/p&gt;
&lt;h3 id="optimeringsmodellen-pa-et-strre-eksempel"&gt;Optimeringsmodellen på et større eksempel&lt;/h3&gt;
&lt;p&gt;La oss kjøre optimeringsmodellen på samme eksempel som tidligere:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
[X \mid \boldsymbol{y}] =
\begin{bmatrix}
  \text{AP} &amp;amp;  \text{Sp} &amp;amp; \text{Frp} &amp;amp; \text{H} &amp;amp; \boldsymbol{y} \\
  -1 &amp;amp;  1 &amp;amp; -2 &amp;amp; -1 &amp;amp; -1\\
  2  &amp;amp;  1 &amp;amp; -2 &amp;amp; -2 &amp;amp; -2\\
  -2 &amp;amp; -2 &amp;amp;  2 &amp;amp;  2 &amp;amp; 2\\
  1  &amp;amp; -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; -2 \\
  1  &amp;amp;  2 &amp;amp;  2 &amp;amp; -2 &amp;amp; 0\\
  -1 &amp;amp; -2 &amp;amp; -2 &amp;amp; 2 &amp;amp; 0
\end{bmatrix} 
\end{align*}&lt;/div&gt;
&lt;p&gt;Svaret er gitt nedenfor, og samsvarer godt med rangeringen som vi fikk tidligere.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;1. H   (51 %)
2. Frp (45 %)
4. Sp  ( 4 %)
3. AP  ( 0 %)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="forskjellen-pa-distansebasert-og-kombinasjonsbasert-forklaring"&gt;Forskjellen på distansebasert og kombinasjonsbasert forklaring&lt;/h3&gt;
&lt;p&gt;Vi har sett to måter å forklare en brukers resultater på i en valgomat:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Distansebasert forklaring&lt;/strong&gt; - rangerer partiene etter distanse fra brukeren. Svarer på spørsmålet “Hvilket enkeltparti er nærmest meg?”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kombinasjonsbasert forklaring&lt;/strong&gt; - brukeren uttrykkes som den nærmeste konvekse kombinasjonen av partiene. Svarer på spørsmålet “Hvilken koalisjon av partier er nærmest meg?”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Forskjellen mellom forklaringene er illustrert i figuren nedenfor.
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/opt_model_vs_distance.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distansebasert forklaring sier at &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; er nærmest &lt;span class="math"&gt;\(B\)&lt;/span&gt;, deretter &lt;span class="math"&gt;\(A\)&lt;/span&gt; og til slutt &lt;span class="math"&gt;\(C\)&lt;/span&gt;. Distansebasert forklaring er &lt;em&gt;innbyrdes uavhengig&lt;/em&gt;; avstanden til &lt;span class="math"&gt;\(B\)&lt;/span&gt; påvirker ikke avstanden til &lt;span class="math"&gt;\(A\)&lt;/span&gt;. Dersom &lt;span class="math"&gt;\(B\)&lt;/span&gt; hadde forsvunnet ville avstanden til &lt;span class="math"&gt;\(A\)&lt;/span&gt; vært uendret.&lt;/li&gt;
&lt;li&gt;Kombinasjonsbasert forklaring uttrykker standpunktet &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; som en konveks kombinasjon. Svaret i figuren ovenfor blir &lt;span class="math"&gt;\(\boldsymbol{\beta} = (\beta_A, \beta_B, \beta_C) = (0, 0.7, 0.3)\)&lt;/span&gt;. Optimeringsmodellens forklaring er &lt;em&gt;innbyrdes avhengig&lt;/em&gt;. Dersom &lt;span class="math"&gt;\(B\)&lt;/span&gt; hadde forsvunnet ville forklaringen lent seg på &lt;span class="math"&gt;\(A\)&lt;/span&gt;, men når &lt;span class="math"&gt;\(B\)&lt;/span&gt; finnes trengs ikke &lt;span class="math"&gt;\(A\)&lt;/span&gt; i det hele tatt for å forklare &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Figuren nedenfor viser et eksempel der forklaringene blir helt ulike.
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/opt_model_vs_distance_different_result.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 350px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Målt i distanse er &lt;span class="math"&gt;\(C\)&lt;/span&gt; nærmere &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; enn både &lt;span class="math"&gt;\(A\)&lt;/span&gt; og &lt;span class="math"&gt;\(B\)&lt;/span&gt;.
Likevel forklares &lt;span class="math"&gt;\(\boldsymbol{y}\)&lt;/span&gt; ved hjelp av &lt;span class="math"&gt;\(\boldsymbol{\beta} = (\beta_A, \beta_B, \beta_C) = (0.5, 0.5, 0)\)&lt;/span&gt; uten å bruke &lt;span class="math"&gt;\(C\)&lt;/span&gt;.
Korteste distanse forteller deg hvilket enkeltparti som er nærmest.
Optimeringsmodellen forteller deg hvilken kombinasjon av partier som står deg nærmest.&lt;/p&gt;
&lt;h1 id="del-3-hvordan-br-en-valgomat-presentere-sprsmal-for-brukeren"&gt;Del 3: Hvordan bør en valgomat presentere spørsmål for brukeren?&lt;/h1&gt;
&lt;p&gt;I denne seksjonen presenterer vi en algoritme som velger påstander som diskriminerer godt mellom partiene.
Det kan være to grunner til at man ønsker dette:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Man skal bruke et utvalg påstander.&lt;/strong&gt; Om partiene har svart på mange spørsmål, ønsker vi gjerne å begrense oss til et utvalg. Hvordan velger vi gode påstander som differensierer mellom partiene?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tidlig avslutning.&lt;/strong&gt; Man ønsker at brukeren skal kunne avslutte valgomaten etter et vilkårlig antall spørsmål. Hvordan garanterer vi at brukeren har blitt presentert for et allsidig sett med påstander?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For å motivere algoritmen begynner vi med å se på samme eksempel som tidligere i artikkelen.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/vectors_statements_with_trivial_direction.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Spør vi om jernbane til Tromsø eller om fylkeskommunen bør avskaffes, innhenter vi ingen informasjon fordi brukerens svar ikke diskriminerer mellom partiene.
Underrommet som spennes av vektoren &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1)\)&lt;/span&gt; består av slike &lt;em&gt;trivielle påstander&lt;/em&gt;.
Den beste påstanden å presentere for brukeren er derimot “Dropp ferjefri E39 langs Vestlandet”, ettersom denne påstanden ligger lengst i fra &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e} \} )\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="algoritme-for-a-velge-pastander-i-rekkeflge"&gt;Algoritme for å velge påstander i rekkefølge&lt;/h2&gt;
&lt;p&gt;La oss generalisere fra to dimensjoner til et vilkårlig antall dimensjoner:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start med det trivielle underrommet som spennes av påstanden &lt;span class="math"&gt;\(\boldsymbol{e} = (1, 1, \ldots, 1)\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Den første påstanden som velges er den som er lengst fra &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e} \} )\)&lt;/span&gt;. Dette er &lt;span class="math"&gt;\(\boldsymbol{v}_1 = \arg \max_j \| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2\)&lt;/span&gt;, der &lt;span class="math"&gt;\(P_S\)&lt;/span&gt; er en projeksjonsmatrise som projiserer en vektor på &lt;span class="math"&gt;\(S\)&lt;/span&gt;. Formelen for en slik projeksjonsmatrise er &lt;span class="math"&gt;\(P_S = S S^{+} = S (S^T S)^{-1} S^T\)&lt;/span&gt;, der kolonnene i matrisen &lt;span class="math"&gt;\(S\)&lt;/span&gt; er vektorene som spenner rommet vi ønsker å projisere på (her bare &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt;).&lt;/li&gt;
&lt;li&gt;Den neste påstanden som velges skal være langt fra både det trivielle underrommet &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt;, og langt fra den forrige valgte påstanden &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt;. Vi velger altså påstanden som er lengst fra &lt;span class="math"&gt;\(\operatorname{span} (\{ \boldsymbol{e}, \boldsymbol{v}_1 \} )\)&lt;/span&gt;. I matematisk notasjon blir dette &lt;span class="math"&gt;\(\boldsymbol{v}_2 = \arg \max_j \| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2\)&lt;/span&gt;, der &lt;span class="math"&gt;\(S\)&lt;/span&gt; i denne iterasjonen er en matrise med kolonner &lt;span class="math"&gt;\(\boldsymbol{e}\)&lt;/span&gt; og &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Slik fortsetter vi og velger påstander som er lengst fra de vi allerede har valgt. Dersom &lt;span class="math"&gt;\(\operatorname{rank} \operatorname{span} (\{ \boldsymbol{e}, \boldsymbol{v}_1, \ldots, \boldsymbol{v}_n \} )\)&lt;/span&gt; blir lik dimensjonaliteten til rommet (antall partier), så er alle gjenværende påstander i rommet. Da blir distansen &lt;span class="math"&gt;\(\| P_S \boldsymbol{v}_j - \boldsymbol{v}_j \|_2 \approx 0\)&lt;/span&gt; for alle gjenværende påstander &lt;span class="math"&gt;\(\boldsymbol{v}_j\)&lt;/span&gt;. Løsningen er å behandle de valgte påstandene som en &lt;span class="caps"&gt;FIFO&lt;/span&gt;-kø: vi fjerner den første valgte påstanden &lt;span class="math"&gt;\(\boldsymbol{v}_1\)&lt;/span&gt; og prøver på nytt. Da er neste påstand vi velger alltid langt fra de siste valgte påstandene.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python-kode for algoritmen er vedlagt nederst i artikkelen.
Figuren nedenfor illustrerer konseptet i tre dimensjoner.
Den neste påstanden som velges er alltid den som er lengst i fra rommet som spennes av den trivielle retningen og tidligere valgte påstander.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/pivoted_QR_visualization.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 99%;"/&gt;&lt;/p&gt;
&lt;h2 id="resultater-darlige-pastander"&gt;Resultater - dårlige påstander&lt;/h2&gt;
&lt;p&gt;La oss se på et eksempel med påstander som innbyrdes ligner på hverandre.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/statements_bad_order.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;De første to påstandene er veldig like.
Den tredje påstanden om Erna er på motsatt side av vektorrommet, men gir likevel ikke mye ny informasjon fordi det er nær rommet som spennes av de første to spørsmålene.
Spørsmålet om Erna avdekker altså lite ny informasjon, på tross av at påstanden er på motsatt side av vektorrommet.&lt;/p&gt;
&lt;h2 id="resultater-gode-pastander"&gt;Resultater - gode påstander&lt;/h2&gt;
&lt;p&gt;Her er et utvalg påstander som er ulike hverandre, avdekket av algoritmen ovenfor.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/valgomat_design/statements_orthogonal_order.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 550px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Den første påstanden er langt fra triviell, fordi halvparten av partiene er uenige og den andre halvparten er enige.
Den andre påstanden er ulikt besvart av seks partier, og skiller seg både fra den trivielle påstanden og den første påstanden.
Slik fortsetter algoritmen med å velge påstander som hele tiden er annerledes enn det brukeren av valgomaten har blitt presentert for.&lt;/p&gt;
&lt;h1 id="oppsummering-og-referanser"&gt;Oppsummering og referanser&lt;/h1&gt;
&lt;p&gt;Valgomater kvantifiserer politiske meninger.
Partienes svar kan representeres som en matrise, der radene (påstander) og kolonnene (partier) kan tolkes vektorer.
En slik geometrisk tolkning gir oss verdifull innsikt: vi kan definere hva det vil si at en person er nær et parti og vi kan formalisere hva det vil si at en påstand diskriminerer mellom partier.&lt;/p&gt;
&lt;p&gt;Idéen som å se på partier som vektorer og rangere brukeren etter nærhet er rimelig åpenbar, og &lt;a href="https://nrkbeta.no/2019/07/03/slik-snekret-vi-en-valgomat-for-hele-landet/"&gt;for kommunevalget 2019 brukte &lt;span class="caps"&gt;NRK&lt;/span&gt;&lt;/a&gt; &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; som metrikk.
(At NRKbetas artikkel pakker ungdomsskolematematikk inn i fraser som “forklar utregningen for meg som ikke har matteskrekk” og “Jeg får vondt i hodet!” er sørgelig.)
Idéen om å beskrive en bruker som en konveks kombinasjon av partier er min egen.
I praksis er det vanskeligere å implementere i kode, samt mer krevende å forstå for en gjennomsnittlig bruker.
Enkle algoritmer har iboende verdi og distanse-beregning gir en enkel og forståelig forklaring.&lt;/p&gt;
&lt;p&gt;Idéen om å se på påstander som vektorer og velge ut påstander som er langt fra hverandre er min egen.
Inspirasjonen kommer fra &lt;a href="https://en.wikipedia.org/wiki/QR_decomposition"&gt;&lt;span class="caps"&gt;QR&lt;/span&gt;-dekomposisjon med pivotering&lt;/a&gt;, som er en algoritme for å ortogonalisere en matrise.
Pivot-kolonnene i &lt;span class="caps"&gt;QR&lt;/span&gt; kan sees på som en grådig approksimasjon av D-optimalitet i &lt;a href="https://en.wikipedia.org/wiki/Optimal_experimental_design"&gt;optimal eksperimentdesign&lt;/a&gt;, som maksimerer volumet av en høydimensjonell parallellepiped.
Forskjellene mellom min algoritme og &lt;span class="caps"&gt;QR&lt;/span&gt;-dekomposisjon er at (1) vi alltid holder oss borte fra den trivielle påstanden &lt;span class="math"&gt;\(\boldsymbol{e} = \boldsymbol{1}\)&lt;/span&gt; og at (2) vi bruker en &lt;span class="caps"&gt;FIFO&lt;/span&gt;-kø.&lt;/p&gt;
&lt;p&gt;Denne artikkelen fokuserte mest på matematikk, men en valgomat må selvsagt ha politisk relevante påstander, godt grafisk design, potensielt mulighet for å hoppe over påstander eller vekte påstander opp, subjektive analyser av resultatene, og så videre.&lt;/p&gt;
&lt;!--
Matematikken i denne artikkelen er hovedsaklig lineær algebra, med litt optimering.
Tre bøker verdt å lese er:

- [Linear Algebra and Its Applications](https://www.amazon.com/Linear-Algebra-Its-Applications-4th/dp/0030105676) av Strang er en god introduksjon. Seksjon 3.3 om "Projections and Least Squares" er spesielt relevant for denne artikkelen.
- [Introduction to Applied Linear Algebra](https://www.amazon.com/Introduction-Applied-Linear-Algebra-Matrices/dp/1316518965) av Boyd og Vandenberghe er en god oppfølger. Kapittel 12 "Least squares" er relevant.
- [Matrix Computations](https://www.amazon.com/Computations-Hopkins-Studies-Mathematical-Sciences/dp/1421407949) av Golub og Van Loan er de-facto oppslagsverket for beregninger. Seksjon 5.4 "Other Orthogonal Factorizations" er relevant.

--&gt;
&lt;h2 id="kode"&gt;Kode&lt;/h2&gt;
&lt;p&gt;Her er en enkel implementasjon av algoritmen som finner påstander som er langt fra hverandre.
Jeg har ikke vektlagt kjøretid, numerisk stabilitet, caching av beregninger, etc.
Koden er absolutt god nok for bruksområdet, men for større datasett bør man skrive om.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;  &lt;span class="c1"&gt;# Python 3.12&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&lt;/span&gt;  &lt;span class="c1"&gt;# NumPy 2.1.3&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;away_from_last_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&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="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Yields the column indices of A that are as far away from a continually&lt;/span&gt;
&lt;span class="sd"&gt;    updated subspace spanned by (1, 1, 1, ...) and a First-In-First-Out (FIFO)&lt;/span&gt;
&lt;span class="sd"&gt;    queue of length k. When a column index is yielded, it is added to the queue.&lt;/span&gt;

&lt;span class="sd"&gt;    Parameters&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    A : np.ndarray&lt;/span&gt;
&lt;span class="sd"&gt;        A matrix (2D array) with columns [v1 | v2 | ... ].&lt;/span&gt;
&lt;span class="sd"&gt;    k : int, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Numbers of elements to keep in the FIFO queue. None means no limit.&lt;/span&gt;
&lt;span class="sd"&gt;    verbose : bool, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Whether to print information. The default is False.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; A = np.array([[3, 0, 0], [0, 2, 0], [0, 0, 1], [2.5, 0, 0]]).T&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=None))&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 1, 3, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=0)) #  Distance from span({(1, 1, 1)}) only&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 3, 1, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; list(away_from_last_k(A, k=1))&lt;/span&gt;
&lt;span class="sd"&gt;    [0, 1, 3, 2]&lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;
    &lt;span class="n"&gt;subspace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="n"&gt;maxlen&lt;/span&gt;&lt;span class="o"&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;# Vectors in FIFO subspace&lt;/span&gt;
    &lt;span class="n"&gt;ones&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&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;shape&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="c1"&gt;# Vector of ones, always in subspace&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;enumerate&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;T&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Remaining column indices of A&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tuple_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tuple_&lt;/span&gt;
        &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vstack&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;  &lt;span class="c1"&gt;# Full space as col. mat.&lt;/span&gt;
        &lt;span class="n"&gt;P_S&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pinv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Projection matrix onto span(S)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;P_S&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Distance between v and span(S)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;remaining&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;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;inds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Col idx of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; remaining vectors: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;inds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Vectors in subspace (excl. ones): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Get idx of col vector furthest from S = [ones | subspace]&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;maxdist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;distance_from_subspace&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxdist&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Distance was 0. Popping from subspace."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Col idx &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; furthest from subspace. Dist: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;maxdist&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.6f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;subspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Mon, 07 Apr 2025 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2025-04-07:/articles/2025/hvordan-fungerer-en-valgomat</guid><category>articles</category><category>mathematics</category></item><item><title>Rettferdig fordeling av boliglån med ulik eierandel</title><link>https://tommyodland.com/articles/2025/rettferdig-fordeling-av-boliglan-med-ulik-eierandel</link><description>&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/unsplash/house_keys.jpg" style="display: block; margin-left: auto; margin-right: auto; max-width: 450px; width: 95%;"/&gt;&lt;/p&gt;
&lt;p&gt;Anta at to personer skal kjøpe en bolig sammen, men har ulik egenkapital og/eller ulik betalingsevne på boliglånet.
Hva er en økonomisk rettferdig fordeling?&lt;/p&gt;
&lt;h2 id="ulik-egenkapital-lik-fordeling-av-lanet"&gt;Ulik egenkapital, lik fordeling av lånet&lt;/h2&gt;
&lt;p&gt;I DNBs artikkel “&lt;a href="https://www.dnb.no/dnbnyheter/no/din-okonomi/del-boliglanet-rettferdig"&gt;del boliglånet rettferdig&lt;/a&gt;” blir følgende fordeling foreslått som rettferdig dersom partene ikke har lik egenkapital.
Alle tall er i tusen kroner.&lt;/p&gt;
&lt;table class="table-striped table-hover table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Johan&lt;/th&gt;
&lt;th&gt;Andrea&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Andel av lån&lt;/td&gt;
&lt;td&gt;1 500&lt;/td&gt;
&lt;td&gt;1 500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egenkapital&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total andel av kjøpesum&lt;/td&gt;
&lt;td&gt;2 300&lt;/td&gt;
&lt;td&gt;1 700&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Andel ift. totalsum&lt;/td&gt;
&lt;td&gt;2 300 / 4 000&lt;/td&gt;
&lt;td&gt;1 700 / 4 000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eierandel&lt;/td&gt;
&lt;td&gt;57,5 %&lt;/td&gt;
&lt;td&gt;42,5 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Men dette kan oppleves som urettferdig.
For å forstå hvorfor gjør vi eksempelet mer ekstremt—hva om Johan har mye mer penger enn Andrea?&lt;/p&gt;
&lt;p&gt;Da kan vi få en fordeling av lån og egenkapital som for eksempel ser slik ut:&lt;/p&gt;
&lt;table class="table-striped table-hover table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Johan&lt;/th&gt;
&lt;th&gt;Andrea&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Andel av lån&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egenkapital&lt;/td&gt;
&lt;td&gt;3 800&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total andel av kjøpesum&lt;/td&gt;
&lt;td&gt;3 900&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Andel ift. totalsum&lt;/td&gt;
&lt;td&gt;3 900 / 4 000&lt;/td&gt;
&lt;td&gt;100 / 4 000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eierandel&lt;/td&gt;
&lt;td&gt;97,5 %&lt;/td&gt;
&lt;td&gt;2,5 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Johan har gått inn med 97,5 %, og skal få 97,5 % av salget.
Men Andrea har likevel gjort et kupp! 
Selv om hun får lite når boligen selges, så &lt;em&gt;hun bor i praksis nesten gratis&lt;/em&gt;.
Hun bruker tross alt halve boligen i hverdagen, men betaler nesten ikke noe for det.&lt;/p&gt;
&lt;h2 id="en-mer-rettferdig-fordeling"&gt;En mer rettferdig fordeling?&lt;/h2&gt;
&lt;p&gt;For å korrigere for skjevheten som oppstår når Andrea disponerer halve boligen kan vi anse henne som delvis å være leietaker.
Hun betaler da leie for den andelen av hennes 50 % bruksrett som ikke er dekket av eierandelen.
Utregningen ser slik ut i Python:&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;# Egenkapital for begge personene&lt;/span&gt;
&lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3_800&lt;/span&gt;
&lt;span class="n"&gt;egenkapital_B&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;egenkapital&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;egenkapital_B&lt;/span&gt;

&lt;span class="c1"&gt;# Andel av lån for begge personene&lt;/span&gt;
&lt;span class="n"&gt;lån_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;lån_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;lån&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lån_A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lån_B&lt;/span&gt;

&lt;span class="c1"&gt;# Utregninger&lt;/span&gt;
&lt;span class="n"&gt;boligverdi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;egenkapital&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lån&lt;/span&gt;
&lt;span class="n"&gt;eierskap_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lån_A&lt;/span&gt;
&lt;span class="n"&gt;eierskap_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;egenkapital_B&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lån_B&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Boligens verdi: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;boligverdi&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Belåningsgrad: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lån&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;boligverdi&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Person A eier &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;eierskap_A&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;boligverdi&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Person B eier &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;eierskap_B&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;boligverdi&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Person A skal betale &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lån_A&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;lån&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; av lånet hver måned"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Person B skal betale &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lån_B&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;lån&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; av lånet hver måned&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Total leiepris per måned for tilsvarende bolig&lt;/span&gt;
&lt;span class="n"&gt;leiepris&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
&lt;span class="n"&gt;leieprosent_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&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="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="n"&gt;eierskap_A&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;boligverdi&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;leieprosent_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&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="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="n"&gt;eierskap_B&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;boligverdi&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;leieprosent_A&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Person A er &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;leieprosent_A&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; leietaker"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Person A betaler &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;leieprosent_A&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;leiepris&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="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; til person B i leie"&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;leieprosent_B&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Person B er &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;leieprosent_B&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.1%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; leietaker"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Person B betaler &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;leieprosent_B&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;leiepris&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="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; til person A i leie"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Kjører man koden blir resultatet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Boligens verdi: 4000.00
Belåningsgrad: 5.00%

Person A eier 97.5%
Person B eier 2.5%

Person A skal betale 50.0% av lånet hver måned
Person B skal betale 50.0% av lånet hver måned

Person B er 95.0% leietaker
Person B betaler 7.12 til person A i leie
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I dette eksempelet ville en tilsvarende bolig kostet 15 tusen å leie per måned.
Andrea disponerer halve boligen, og av denne halvdelen leier hun i praksis 95 % og eier 5 %.
Hun eier altså 5 % av sin 50 % bruksandel, som blir 0,05 * 0,5 = 2,5 % av boligens totalverdi.
&lt;strong&gt;Hun må betale 7,12 tusen per måned i leie til Johan.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="det-originale-eksempelet"&gt;Det originale eksempelet&lt;/h3&gt;
&lt;p&gt;La oss gå tilbake til det originale eksempelet der Andrea går inn med 200 tusen i egenkapital og Johan går inn med 800 tusen.
Tallene ser da slik ut:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;
&lt;span class="n"&gt;egenkapital_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="n"&gt;lån_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
&lt;span class="n"&gt;lån_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Utregningen blir nå at Andrea eier 42,5 % av boligen.
Men med en antatt månedlig leiekostnad på 15 tusen må hun både (1) betale ned halvparten av lånet per måned og (2) betale &lt;strong&gt;1,13 tusen kroner i leie til Johan&lt;/strong&gt; ettersom han gikk inn med en større andel egenkapital.&lt;/p&gt;
&lt;h3 id="et-ekstremt-eksempel"&gt;Et ekstremt eksempel&lt;/h3&gt;
&lt;p&gt;Hva om Andrea ikke går inn med noe penger i det hele tatt?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;egenkapital_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1_000&lt;/span&gt;
&lt;span class="n"&gt;egenkapital_B&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;lån_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3_000&lt;/span&gt;
&lt;span class="n"&gt;lån_B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Da må Andrea betale 7,5 tusen i leie til Johan hver måned, fordi det er halvparten av boligens antatte leiekostnad.
Hadde vi fulgt DNBs logikk ville hun bodd gratis.&lt;/p&gt;
&lt;h2 id="konklusjon"&gt;Konklusjon&lt;/h2&gt;
&lt;p&gt;DNBs eksempel tar ikke hensyn til at parten som eier den minste andelen i boligen disponerer halve boligen.
For å korrigere for dette kan vi anse parten som eier minst som å delvis være leietaker.
Om leietakeren eier 42,5 % av boligen men 50 % av lånet, slik som i DNBs eksempel, så får hun 42,5 % av salgssummen og betaler 42,5 % av vedlikeholdskostnadene.
Hun betaler også 50 % av lånet, pluss leie til den andre parten.&lt;/p&gt;
&lt;p&gt;Dersom egenkapital er begrensningen, er en annen mulighet at den ene parten låner penger av den andre parten.
Da kan begge gå inn med 50 % i både egenkapital og lån, men det er tatt opp et privat lån på siden.
I praksis kan det oppleves kjipt å være delvis leietaker, spesielt om eierbrøken uansett er tilnærmet lik 50 %.
Da er det nok mer praktisk å følge DNBs utregning.&lt;/p&gt;
&lt;p&gt;Rent matematisk er lærdommen at man bør konstruere ekstreme eksempler for å sjekke om en utregning gir mening.
DNBs utregning feiler på denne testen og er åpenbart utrettferdig når eierbrøken blir for skjev.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Se også “&lt;a href="https://pengeverkstedet.no/bolig/slik-deler-dere-boliglanet-og-utgiftene-riktig/"&gt;Slik deler dere boliglånet og utgiftene riktig&lt;/a&gt;” hos Pengeverkstedet. Artikkelen sier at totalen er lik egenkapital pluss lån, og korrigerer ikke for at hver part disponerer halvparten av boligen.&lt;/li&gt;
&lt;li&gt;Om du vil ha en utfordring kan du forsøke å generalisere logikken ovenfor. Hva blir utregningen om mer enn to personer går sammen med ulik egenkapital og/eller ulik betalingsevne på lån?&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 07 Mar 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-03-07:/articles/2025/rettferdig-fordeling-av-boliglan-med-ulik-eierandel</guid><category>articles</category><category>mathematics</category></item><item><title>Strømstøtte, fastpris, makspris og insentiver</title><link>https://tommyodland.com/articles/2025/stromstotte-fastpris-makspris-og-insentiver</link><description>&lt;p&gt;Strømprisen har tatt stor plass i media de siste årene.
Arbeiderpartiet foreslår nå å bytte ut &lt;strong&gt;strømstøtte&lt;/strong&gt; med &lt;strong&gt;fastpris&lt;/strong&gt;, mens Fremskrittspartiet og Kristelig Folkeparti vil ha &lt;strong&gt;makspris&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I denne artikkelen skal vi visualisere disse ordningene, fikse de svake insentivene ved hjelp av litt enkel matematikk på &lt;span class="caps"&gt;VGS&lt;/span&gt;-nivå, og se at politikernes tenkemåte rundt strømpriser har fellestrekk med hvordan de tenker på f.eks. fraværsgrensa.&lt;/p&gt;
&lt;h2 id="konstant-pris-liner-pris-og-insentiver"&gt;Konstant pris, lineær pris og insentiver&lt;/h2&gt;
&lt;p&gt;I Oslo kommune er det to måter å fastsette &lt;a href="https://www.oslo.kommune.no/vann-og-avlop/tilknytningsgebyr-arsgebyr-og-vannmaler/vann-og-avlopsgebyrer/#toc-1"&gt;vann- og avløpsgebyrer&lt;/a&gt; på:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Konstant pris&lt;/strong&gt; basert på en arealberegning, som er uavhengig av forbruk&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lineær pris&lt;/strong&gt; proporsjonal med forbruk (pluss en fastpris)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I motsetning til vannpris, er strømpris alltid basert på kundens forbruk.
Om prisen var konstant, ville det ikke vært noe økonomisk insentiv for å isolere, holde temperaturen nede, kjøpe varmepumpe, eller spare strøm på andre måter.&lt;/p&gt;
&lt;p&gt;Strømprisen har en annen egenskap som vannprisen ikke har; den er ustabil over tid.
Prisen varierer innad i døgnet, innad året, og fra år til år.&lt;/p&gt;
&lt;h2 id="dagens-strmsttte"&gt;Dagens strømstøtte&lt;/h2&gt;
&lt;p&gt;Figuren nedenfor viser prisen per kvartal de siste ti årene. 
Dataene er fra &lt;a href="https://www.nrk.no/norge/sjekk-hvor-mange-tusenlapper-du-kan-spare-med-ny-fastpris-1.17243502"&gt;NRKs.no&lt;/a&gt; / &lt;span class="caps"&gt;SSB&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fastpris_strom/Strømstøttestatendekker90avprisenover73øre.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"/&gt;&lt;/p&gt;
&lt;p&gt;Dagens strømstøtte-ordning er også vist i figuren.
Opp til 73 øre betaler man alt selv, deretter betaler man 10% av overskytende.
La oss kalle spotprisen for &lt;span class="math"&gt;\(x\)&lt;/span&gt; og nettoprisen som kunden betaler for &lt;span class="math"&gt;\(y\)&lt;/span&gt;.
Da kan formelen for nettopris skrives som&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = \min\left( x, 73 + 0.1 (x - 73) \right).
\end{equation*}&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Fra og med 1. januar 2025 er strømstøtten &lt;a href="https://www.hvakosterstrommen.no/artikler/slik-fungerer-stromstotten"&gt;75 øre&lt;/a&gt;, ikke 73 øre.&lt;/p&gt;
&lt;h2 id="fastpris"&gt;Fastpris&lt;/h2&gt;
&lt;p&gt;Arbeidetpartiet lanserte januar 2025 forslag om &lt;a href="https://www.nrk.no/norge/dette-er-store-sin-plan-for-a-kuppe-straumveljarane-1.17240357"&gt;fastprisavtaler på 40 øre/kWh&lt;/a&gt;.
En slik avtale er visualisert i &lt;a href="https://www.nrk.no/norge/sjekk-hvor-mange-tusenlapper-du-kan-spare-med-ny-fastpris-1.17243502"&gt;denne artikkelen&lt;/a&gt; og gjenskapt i figuren nedenfor.
Formelen er enkel; uansett hva spotprisen &lt;span class="math"&gt;\(x\)&lt;/span&gt; er, blir nettoprisen &lt;span class="math"&gt;\(y\)&lt;/span&gt; lik &lt;span class="math"&gt;\(40\)&lt;/span&gt; øre/kWh.&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = 40
\end{equation*}&lt;/div&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fastpris_strom/Fastprispå40ørekWh.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"/&gt;&lt;/p&gt;
&lt;h2 id="makspris"&gt;Makspris&lt;/h2&gt;
&lt;p&gt;Frp og KrF ønsker &lt;a href="https://www.nrk.no/norge/full-sprik-om-stromstotten-1.17207031"&gt;100 prosent kompensasjon for priser over 50 øre/kWh&lt;/a&gt;, altså en makspris.
Formelen blir&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = \min\left(x, 50 \right),
\end{equation*}&lt;/div&gt;
&lt;p&gt;og resultatet av en slik regel er vist i figuren nedenfor.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fastpris_strom/Maksprispå50ørekWh.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"/&gt;&lt;/p&gt;
&lt;h2 id="insentiver-og-den-deriverte"&gt;Insentiver og den deriverte&lt;/h2&gt;
&lt;p&gt;Fastprisforslaget får &lt;a href="https://www.dn.no/politikk/strom/strompris/strompriser/sjefokonom-om-fastprisordning-kan-ha-klare-negative-bieffekter/2-1-1773088"&gt;kritikk for å ta bort insentiver&lt;/a&gt;.
Kritikken kommer fra professorer, økonomer, og fra Støre selv, som tidligere uttalte at:&lt;/p&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;&lt;span class="dquo"&gt;“&lt;/span&gt;Makspris på strøm løser ikke dette problemet, makspris gjør ikke noe med gassprisene og det fyller heller ikke vannmagasinene.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dette er riktig, men vi må huske at det er snakk om en &lt;strong&gt;fastpris per kilowattime&lt;/strong&gt;, ikke en &lt;strong&gt;fastpris per måned&lt;/strong&gt;.
En fastpris per kilowattime insentiverer til lavt forbruk; kutter man forbruket til halvparten, betaler man også halvparten.
Derimot forsvinner insentiver for å bruke strøm når den er billig, heller enn når den er dyr, under en slik ordning.&lt;/p&gt;
&lt;p&gt;Politikere klamrer seg ofte til harde grenser som fastpriser og makspriser når de vil regulere et marked.
Ofte tenker de at det må gå en hard grense mellom regulering og frie markeder i seg selv.
Her er noen eksempler der harde grenser kunne vært myknet opp:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skoleopptak.&lt;/strong&gt; Politikerne i Oslo har i mange år ment at skoleopptak enten må være helt karakterbasert, eller basert på ren loddtrekning. En mellomting er mulig med &lt;a href="https://tommyodland.com/articles/2020/fritt-skolevalg-del-2-karakterer"&gt;vektet loddtrekning&lt;/a&gt;, som jeg skrev om i 2020.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fraværsgrensa.&lt;/strong&gt; En fraværsprosent på 9% er uproblematisk, men med 11% fravær &lt;a href="https://snl.no/frav%C3%A6rsgrense"&gt;mister man hele standpunktkarakteren&lt;/a&gt;. En mykere overgang ville vært mulig ved å redusere karakteren noe før man mister hele karakteren.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arbeidsavklaringspenger (&lt;span class="caps"&gt;AAP&lt;/span&gt;).&lt;/strong&gt; Du kvalifiserer om du er 50% syk, men ikke om du er 49% syk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Toll.&lt;/strong&gt; Frem til 1. januar 2024 var det &lt;a href="https://www.toll.no/no/bedrift/nyheter-for-naeringslivet/na-avvikles-siste-del-av-350-kronersgrensen"&gt;350-kronersgrense&lt;/a&gt; for toll- og avgiftsfritak på varer kjøpt fra utlandet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ofte er harde grenser både nødvendig, enkelt og praktisk; for eksempel 18-års aldersgrense på alkohol.
Andre ganger kan harde grenser skape perverse insentiver og uheldige tilpasninger, som når elever strategisk planlegger fravær rett under grensen.
Da bør vi vurdere mer gradvise overganger som bedre reflekterer de underliggende realitetene.
Dette er en enkel form for &lt;a href="https://en.wikipedia.org/wiki/Mechanism_design"&gt;mekanismedesign&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I våre modeller &lt;span class="math"&gt;\(y(x)\)&lt;/span&gt; er insentiver analogt med den &lt;a href="https://snl.no/derivasjon_-_matematikk"&gt;deriverte&lt;/a&gt;, som er stigningen til funksjonen.
En flat funksjon dreper koblingen mellom input og output, som her er bruttopris og nettopris.
For å insentivere strømforbruk når det er billig, kan vi glatte ut Frp og KrFs makspris.&lt;/p&gt;
&lt;h2 id="glatt-makspris"&gt;Glatt makspris&lt;/h2&gt;
&lt;p&gt;Vi kan glatte ut makspris-forslaget til Frp og KrF for å beholde insentiver, og samtidig garantere at prisen aldri overstiger 50 øre/kWh.
Insentivene blir svake, men det er bedre enn ingen ting.&lt;/p&gt;
&lt;p&gt;Vi starter med softplus-funksjonen &lt;span class="math"&gt;\(s(x; \alpha) = \log \left(1 + \exp\left( \alpha x \right) \right) / \alpha\)&lt;/span&gt;, skifter og skalerer til
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = 50 - s(50 - x; \alpha)
\end{equation*}&lt;/div&gt;
&lt;p&gt;
og velger en fornuftig &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;-verdi for å kontrollere hvor glatt funksjonen blir.
Resultatet er vist i figuren nedenfor.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fastpris_strom/Mykmaksprispå50ørekWh.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"/&gt;&lt;/p&gt;
&lt;h2 id="logaritmisk-pris"&gt;Logaritmisk pris&lt;/h2&gt;
&lt;p&gt;Enda sterkere insentiver får man om man ikke begrenser seg til en makspris på 50 øre/kWh.
Det politikerne egentlig ønsker å gjøre er å skjerme forbrukerne mot den enorme prisvariasjonen.
Grunnen til at de strekker seg mot fastpris og makspris heller enn en logaritme, kan være at de ikke vet hva en logaritme er, eller tror at befolkningen ikke vet hva en logaritme er.&lt;/p&gt;
&lt;p&gt;For å begrense svingningene, men samtidig beholde insentiver i større grad enn med makspris, kan vi bruke en logaritmisk pris.
Formelen kan skrives som
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
y(x) = \log \left( 1 + \alpha x \right) / \alpha ,
\end{equation*}&lt;/div&gt;
&lt;p&gt;
der &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;-verdien kontrollerer styrken.
En logaritmisk funksjon vil begrense prisen kraftig, men la kunden føle toppene mer enn med makspris.
Logaritmen er en enkel funksjonell form som oppnår ønsket effekt, men mange andre funksjoner kan også oppnå lignende effekter og være vel så gode.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/fastpris_strom/Logaritmiskpris.png" style="display: block; margin-left: auto; margin-right: auto; max-width: 800px; width: 98%;"/&gt;&lt;/p&gt;
&lt;h2 id="oppsummering"&gt;Oppsummering&lt;/h2&gt;
&lt;p&gt;Hvordan strømmarkedet skal fungere er en politisk beslutning, og de fleste problemene er uten tvil politisk skapt heller enn noe som kan fikses med matematikk.
Når det er sagt er det fremdeles interessant å analysere politikernes tankemåte matematisk, og det er nok lurt å lytte til økonomenes betraktninger. 
Politikere tyr ofte til harde grenser som fastpris og makspris, men vi har sett at enkel matematikk kan gi oss mer nyanserte løsninger.&lt;/p&gt;
&lt;p&gt;Vi skisserte to prismekanismer som både beskytter forbrukerne mot ekstreme prissvingninger og samtidig bevarer insentiver for fornuftig strømbruk: en myk makspris og en logaritmisk pris.
Samme tankegang kunne forbedret andre områder der harde grenser skaper uheldige virkninger.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Tue, 04 Feb 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-02-04:/articles/2025/stromstotte-fastpris-makspris-og-insentiver</guid><category>articles</category><category>mathematics</category></item><item><title>Beer tasting</title><link>https://tommyodland.com/articles/2025/beer-tasting</link><description>&lt;p&gt;In this article we’ll analyze data from a blind test.
Blind tests are fun experiments that can determine, for instance, if family members or friends can distinguish between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pepsi and Cola&lt;/li&gt;
&lt;li&gt;Red and white wines&lt;/li&gt;
&lt;li&gt;Brand-name and generic foods&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Analysis of such data is interesting, especially when you’ve designed the experiment and collected the data yourself.
You might learn some statistics along the way, and that knowledge could prove useful in industry settings as well as in everyday situations.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/beer_tasting/beer2.jpg" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width:400px;"/&gt;&lt;/p&gt;
&lt;h2 id="the-beer-tasting-experiment"&gt;The beer tasting experiment&lt;/h2&gt;
&lt;p&gt;A friend wanted to determine her true beer preferences, so we decided to run a blind test:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We bought 6 different brands of beer.&lt;/li&gt;
&lt;li&gt;She tasted each brand on 3 different occasions.&lt;/li&gt;
&lt;li&gt;The ordering of the 18 tastings was randomized.&lt;/li&gt;
&lt;li&gt;Each of the 18 tastings was conducted on a separate day.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In every tasting, the unknown beer brand was given a rating on a scale from 1 to 10.
Knowledge of which brands were given which rating was not disclosed until the experiment was finished.
The results of the full experiment are shown in the figure below, and the full dataset is available at the end of this article.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/beer_tasting/beer_ratings.png" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width:600px;"/&gt;&lt;/p&gt;
&lt;p&gt;The Oslo-based beer brand “Schous” is the winner, with an average rating of &lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
(7 + 7 + 8)/3 = 22/3 \approx 7.33
\end{align*}&lt;/div&gt;
&lt;h2 id="can-my-friend-tell-beers-apart"&gt;Can my friend tell beers apart?&lt;/h2&gt;
&lt;p&gt;Let us examine the figure above a bit more critically.
The ratings are somewhat consistent, in the sense that they do not look like random integers between 1 and 10.
However, it is surprising that the very same beer is awarded both the lowest observed rating (3) and the highest observed rating (8).&lt;/p&gt;
&lt;p&gt;This raises the question: &lt;strong&gt;can my friend consistently tell beers apart?&lt;/strong&gt;
Let’s test this with a &lt;a href="https://tommyodland.com/articles/2023/three-monte-carlo-permutation-tests"&gt;permutation hypothesis test&lt;/a&gt;.
We’ll test if the explained variance &lt;span class="math"&gt;\(r^2\)&lt;/span&gt; in the dataset is higher than what could expect if knowledge of beer brands contained no information.&lt;/p&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(y_i\)&lt;/span&gt; be the rating at tasting &lt;span class="math"&gt;\(i\)&lt;/span&gt;.
Defint the variable &lt;span class="math"&gt;\(x_{ij}\)&lt;/span&gt; to be a one-hot encoding of the brands: it is equal to &lt;span class="math"&gt;\(1\)&lt;/span&gt; if beer brand &lt;span class="math"&gt;\(j\)&lt;/span&gt; was tasted in tasting &lt;span class="math"&gt;\(i\)&lt;/span&gt;, otherwise it is equal to &lt;span class="math"&gt;\(0\)&lt;/span&gt;.
We’ll assume a linear model with structure
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
y_i = \sum_j \beta_j x_{ij} + \epsilon_i,
\end{align*}&lt;/div&gt;
&lt;p&gt;
where &lt;span class="math"&gt;\(\epsilon_i\)&lt;/span&gt; is normally distributed noise.
The coefficients &lt;span class="math"&gt;\(\beta_j\)&lt;/span&gt; have a natural interpretation: they are the average ratings of each brand &lt;span class="math"&gt;\(j\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Fitting this model to the data gives an explained variance &lt;span class="math"&gt;\(r^2 = 0.375\)&lt;/span&gt;.
The beer brand explains around one third of the total variane in the ratings.
To put this number into perspective, consider that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A perfectly consistent beer-rater would achieve &lt;span class="math"&gt;\(r^2 = 1\)&lt;/span&gt; on a training set (and on a test set), since the beer brand would explain their ratings perfectly and account for all variation.&lt;/li&gt;
&lt;li&gt;A completely random beer-rater would on average achieve &lt;span class="math"&gt;\(r^2 \approx 0\)&lt;/span&gt; on a test set, since the beer brand would not explain the ratings at all. However, on a training set they would achieve &lt;span class="math"&gt;\(r^2 &amp;gt; 0\)&lt;/span&gt; since there are six degrees of freedom in the brand coefficients &lt;span class="math"&gt;\(\beta_j\)&lt;/span&gt;, and these will overfit and capture some of the variance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let the null hypothesis &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; be that the beer brands have no effect on the ratings at all.
Permuting the rows of the data set randomly (so there is no relationship between brands and ratings) produces the following distribution of &lt;span class="math"&gt;\(r^2\)&lt;/span&gt; under the null hypothesis.
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/beer_tasting/beer_tasting_r2_scores.png" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width:600px;"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; is true (brands have no effect), we will on average observe an &lt;span class="math"&gt;\(r^2\)&lt;/span&gt; of around &lt;span class="math"&gt;\(0.292\)&lt;/span&gt;. The variation in &lt;span class="math"&gt;\(r^2\)&lt;/span&gt;-values in the permuted data sets is large, and anything between &lt;span class="math"&gt;\(0.08\)&lt;/span&gt; and &lt;span class="math"&gt;\(0.57\)&lt;/span&gt; is relatively common (coverage of 90%).&lt;/li&gt;
&lt;li&gt;The observed &lt;span class="math"&gt;\(r^2\)&lt;/span&gt; is &lt;span class="math"&gt;\(0.375\)&lt;/span&gt;, which is certainly higher than the average, but by no means extreme.&lt;/li&gt;
&lt;li&gt;The &lt;span class="math"&gt;\(p\)&lt;/span&gt;-value, which is the probability of observing a value of &lt;span class="math"&gt;\(0.375\)&lt;/span&gt; or higher, given that &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; is true, is around &lt;span class="math"&gt;\(0.289\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scientists typically reject &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; if the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-value is lower than &lt;span class="math"&gt;\(0.05\)&lt;/span&gt;.
Here the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-value is much larger; this level of consistency in the ratings is well within the realm of what could happen even if my friend cannot tell beers apart.
It might be that any apparent consistency is attributable to chance rather than genuine tasting ability.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;The brand “Schous” is the winner, but there is a concerning amount of variation in the ratings within each brand.
It’s not clear that my friend can consistently tell beers apart in this experiment. The &lt;span class="math"&gt;\(p\)&lt;/span&gt;-value is so high that any scientific paper would report a “no effect” conclusion, failing to discard &lt;span class="math"&gt;\(H_0\)&lt;/span&gt;.
That being said, I am also reluctant to decisively claim that my friend cannot tell beers apart. While &lt;span class="math"&gt;\(H_0\)&lt;/span&gt; is mathematically convenient, it imposes a somewhat one-sided burden of proof.&lt;/p&gt;
&lt;p&gt;People’s tasting abilities may be less consistent than they believe.
The paper &lt;a href="https://www.cambridge.org/core/journals/journal-of-wine-economics/article/abs/an-examination-of-judge-reliability-at-a-major-us-wine-competition/15EF999FBAC63C5FB6DCF1C2F4BB6655"&gt;An Examination of Judge Reliability at a major &lt;span class="caps"&gt;U.S.&lt;/span&gt; Wine Competition&lt;/a&gt; investigates this to some extent.
A larger, more involved experiment might be needed to figure out if my friend can tell beers apart.
For now, the data suggests Schous remains the preferred choice.&lt;/p&gt;
&lt;h2 id="dataset"&gt;Dataset&lt;/h2&gt;
&lt;p&gt;Here is the dataset used in this article in &lt;code&gt;.csv&lt;/code&gt; format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;order,beer_brand,rating
1,Frydenlund,3
2,CB,7
3,Pokal,5
4,Tuborg,3
5,Frydenlund,5
6,Tuborg,8
7,Frydenlund,5
8,Hansa,6
9,CB,7
10,CB,5
11,Pokal,3
12,Schous,7
13,Hansa,3
14,Schous,8
15,Hansa,8
16,Tuborg,4
17,Schous,7
18,Pokal,5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="python-code"&gt;Python code&lt;/h2&gt;
&lt;p&gt;Here’s a sketch of Python code to perform the statistical test.
Python 3.12.8 was used with Pandas 2.2.3, NumPy 1.26.4 and scikit-learn 1.5.2. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;pandas&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="nn"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.preprocessing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OneHotEncoder&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sklearn.linear_model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LinearRegression&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&lt;/span&gt;

&lt;span class="c1"&gt;# Read dataset&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"dataset.csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create X and y&lt;/span&gt;
&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OneHotEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s2"&gt;"beer_brand"&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;

&lt;span class="c1"&gt;# Fit linear model, compute r2&lt;/span&gt;
&lt;span class="n"&gt;linreg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LinearRegression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fit_intercept&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;linreg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linreg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"r^2=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.3f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# r^2=0.375&lt;/span&gt;

&lt;span class="c1"&gt;# Randomly permute data and fit models&lt;/span&gt;
&lt;span class="n"&gt;rng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default_rng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;r2_scores&lt;/span&gt; &lt;span class="o"&gt;=&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;simulation&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;999&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;y_permuted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rng&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;permutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;linreg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_permuted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r2_scores&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linreg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_permuted&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;p_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&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="n"&gt;np&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;r2_scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"p_value=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p_value&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.3f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# p_value=0.311&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Fri, 10 Jan 2025 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2025-01-10:/articles/2025/beer-tasting</guid><category>articles</category><category>statistics</category></item><item><title>Papers read in 2024</title><link>https://tommyodland.com/articles/2024/papers-read-in-2024</link><description>&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/unsplash/unsplash_16_3_1600px_bookshelf.jpg" style="display: block; margin-left: auto; margin-right: auto;"/&gt;&lt;/p&gt;
&lt;p&gt;I try to read a few math books every year, but the more I learn, the more overlap I see in their content.
In 2024 I decided to organize with a group of friends and read papers instead.&lt;/p&gt;
&lt;p&gt;We read 17 papers together, and I skimmed many more to decide on which 17 to read in the group.
Below is the list of papers, which might be useful if you want to learn some new data science related conceps, or if you want you organize a reading club of your own.&lt;/p&gt;
&lt;h2 id="lessons-learned"&gt;Lessons learned&lt;/h2&gt;
&lt;p&gt;Here’s what I learned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Most papers are not ideal for learning; the authors are not great writers and they have limited space to convey their ideas.&lt;/li&gt;
&lt;li&gt;Most practitioners should read books rather than papers to gain general knowledge.&lt;/li&gt;
&lt;li&gt;Finding papers that are on an appropriate technical level and of interest to everyone in a group is difficult.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before reading papers, it might be useful to read some &lt;em&gt;guides on reading papers&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://ccr.sigcomm.org/online/files/p83-keshavA.pdf"&gt;How to Read a Paper&lt;/a&gt; (2007)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cs.jhu.edu/~jason/advice/how-to-read-a-paper.html"&gt;How to Read a Technical Paper&lt;/a&gt; (2009)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jtleek/readingpapers"&gt;A guide to reading scientific papers &lt;/a&gt; (2016)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://saiamrit.github.io/technical-blog/research/reading_papers/2021/07/31/read-papers.html"&gt;How to read Machine Learning and Deep Learning Research papers&lt;/a&gt; (2021)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the list of papers that we read.
&lt;em&gt;Honorable mentions&lt;/em&gt; are papers that I was interested in, but ended up not choosing for the group.
Papers that I for some reason enjoyed more than others get a star (⭐).&lt;/p&gt;
&lt;hr/&gt;
&lt;h2 id="data-science"&gt;Data Science&lt;/h2&gt;
&lt;p&gt;We chose to start with two papers on the culture of data science and practical tips for data science projects.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1080/10618600.2017.1384734"&gt;50 Years of Data Science&lt;/a&gt;&lt;/strong&gt;. David Donoho (2017)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://doi.org/10.1371/journal.pcbi.1005510"&gt;Good enough practices in scientific computing&lt;/a&gt;&lt;/strong&gt;. Wilson et al. (2017)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions"&gt;Honorable mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://homes.cs.washington.edu/~pedrod/papers/cacm12.pdf"&gt;A few useful things to know about machine learning&lt;/a&gt;&lt;/strong&gt;. Pedro Domingos (2012)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://doi.org/10.1214/ss/1009213726"&gt;Statistical Modeling: The Two Cultures&lt;/a&gt;&lt;/strong&gt;. Leo Breiman (2001)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="recommendation-engines"&gt;Recommendation engines&lt;/h2&gt;
&lt;p&gt;We looked into recommendation engines: the Netflix price, matrix factorization and extensions to neural networks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1109/MC.2009.263"&gt;Matrix Factorization Techniques for Recommender Systems&lt;/a&gt;&lt;/strong&gt;. Koren et al. (2009)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://doi.org/10.1109/ICDM.2010.127"&gt;Factorization Machines&lt;/a&gt;&lt;/strong&gt;. Steffen Rendle (2010)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1145/3038912.3052569"&gt;Neural Collaborative Filtering&lt;/a&gt;&lt;/strong&gt;. He et al. (2017) [&lt;a href="https://arxiv.org/abs/1708.05031"&gt;arXiv&lt;/a&gt;]&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions_1"&gt;Honorable mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://ieeexplore.ieee.org/document/4781121"&gt;Collaborative Filtering for Implicit Feedback Datasets&lt;/a&gt;&lt;/strong&gt;. Hu et al. (2008)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/1708.05027"&gt;Neural Factorization Machines for Sparse Predictive Analytics&lt;/a&gt;&lt;/strong&gt;. He et al. (2017)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="continuous-optimization"&gt;Continuous optimization&lt;/h2&gt;
&lt;p&gt;These articles cover practical methods for solving optimization problems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/1609.04747"&gt;An overview of gradient descent optimization algorithms&lt;/a&gt;&lt;/strong&gt;. Sebastian Ruder (2016)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://tminka.github.io/papers/logreg/minka-logreg.pdf"&gt;A comparison of numerical optimizers for logistic regression&lt;/a&gt;&lt;/strong&gt;. Thomas P. Minka (2003)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1109/JSTSP.2007.910971"&gt;An Interior-Point Method for Large-Scale l1-Regularized Least Squares&lt;/a&gt;&lt;/strong&gt;. Kim et al. (2007)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions_2"&gt;Honorable mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/0711.1612"&gt;Enhancing Sparsity by Reweighted L1 Minimization&lt;/a&gt;&lt;/strong&gt;. Candes et al. (2007)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="operations-research"&gt;Operations research&lt;/h2&gt;
&lt;p&gt;In the age of data science, operations research is underrated.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1287/inte.2016.0875"&gt;&lt;span class="caps"&gt;UPS&lt;/span&gt; Optimizes Delivery Routes&lt;/a&gt;&lt;/strong&gt;. Holland et al. (2017)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://pubsonline.informs.org/doi/10.1287/inte.2017.0897"&gt;Increasing the Responsiveness of Firefighter Services …&lt;/a&gt;&lt;/strong&gt;. van den Berg et al. (2017)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dl.acm.org/doi/10.5555/2887007.2887103"&gt;Data analysis and optimization for (citi)bike sharing&lt;/a&gt;&lt;/strong&gt;. O’Mahony et al. (2015)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions_3"&gt;Honorable mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.1287/inte.1060.0252"&gt;Defending Critical Infrastructure&lt;/a&gt;&lt;/strong&gt;. Brown et al. (2006)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="information-and-compression"&gt;Information and compression&lt;/h2&gt;
&lt;p&gt;Information theory has a rich history and is the foundation of many modern techniques.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf"&gt;A Mathematical Theory of Communication&lt;/a&gt;&lt;/strong&gt;. Claude E. Shannon (1948)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dl.acm.org/doi/10.1145/214762.214771"&gt;Arithmetic coding for data compression&lt;/a&gt;&lt;/strong&gt;. Witten et al. (1987)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="statistics"&gt;Statistics&lt;/h2&gt;
&lt;p&gt;More statistics never hurts, and seeing how practitioners attack problems is always interesting.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.duo.uio.no/handle/10852/10284"&gt;Should the Olympic sprint skaters run 500 meter twice?&lt;/a&gt;&lt;/strong&gt;. Nils Lid Hjort (1994)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://arxiv.org/abs/1701.02434"&gt;A Conceptual Introduction to Hamiltonian Monte Carlo&lt;/a&gt;&lt;/strong&gt;. Michael Betancourt (2017)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/1206.1901"&gt;&lt;span class="caps"&gt;MCMC&lt;/span&gt; using Hamiltonian dynamics&lt;/a&gt;&lt;/strong&gt;. Radford M. Neal (2012)&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;&lt;a href="https://peerj.com/articles/6876/"&gt;Hierarchical generalized additive models in ecology&lt;/a&gt;&lt;/strong&gt;. Pedersen​ et al. (2019)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="honorable-mentions_4"&gt;Honorable mentions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://library.mpib-berlin.mpg.de/ft/gg/GG_Mindless_2004.pdf"&gt;Mindless statistics&lt;/a&gt;&lt;/strong&gt;. Gerd Gigerenzer (2004)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://efron.ckirby.su.domains/papers/2019PredictEstimatAttribut.pdf"&gt;Prediction, Estimation, and Attribution&lt;/a&gt;&lt;/strong&gt;. Bradley Efron (2020)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://bayes.wustl.edu/MacKay/alpha.pdf"&gt;Hyperparameters: Optimize, or Integrate Out?&lt;/a&gt;&lt;/strong&gt; David &lt;span class="caps"&gt;J.C.&lt;/span&gt; MacKay (1996)&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sat, 14 Dec 2024 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2024-12-14:/articles/2024/papers-read-in-2024</guid><category>articles</category><category>datascience</category></item><item><title>Solving NRK’s game “Former”</title><link>https://tommyodland.com/articles/2024/solving-nrks-game-former</link><description>&lt;p&gt;&lt;span class="caps"&gt;NRK&lt;/span&gt; published &lt;a href="https://www.nrk.no/former-1.17105310"&gt;a fun game&lt;/a&gt; on their website called “Former” (English: “Shapes”).
The gameplay is straightforward: the player clicks on a cell on the board, and as a result every connected cell (up, down, left and right) with the same color vanishes.
The surrounding shapes fall down and the player is ready for the next move.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/nrk_game_gameplay.gif" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 431px;"/&gt;&lt;/p&gt;
&lt;p&gt;The goal is to make all shapes vanish in as few clicks as possible.&lt;/p&gt;
&lt;p&gt;In this article we formulate the game as a graph search problem, show how to reduce the size of the graph and attempt to solve it using various graph search algorithms.
&lt;strong&gt;The full code is available at &lt;a href="https://github.com/tommyod/NRK-former-game"&gt;github.com/tommyod/&lt;span class="caps"&gt;NRK&lt;/span&gt;-former-game&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="games-as-directed-graphs"&gt;Games as directed graphs&lt;/h2&gt;
&lt;p&gt;Games like these can be represented as directed graphs: nodes represent game states and directed edges represent moves (clicks).
The initial node is the first board we see and the terminal node is the empty board.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Any path from the initial node to the terminal node is a solution.&lt;/li&gt;
&lt;li&gt;The optimal solution is the shortest solution path.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The original game has 9 rows and 7 columns, which leads to a huge graph and therefore a huge search space.
In the game instance shown in the animation above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There are 39 possible first moves&lt;/li&gt;
&lt;li&gt;There are 1,446 possible sequences of two moves&lt;/li&gt;
&lt;li&gt;There are 51,162 possible sequences of three moves&lt;/li&gt;
&lt;li&gt;There are 1,730,312 possible sequences of four moves&lt;/li&gt;
&lt;li&gt;There are 55,950,299 possible sequences of five moves&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We count one move as one click on any cell in a connected group of cells of the same color.
The search space continues to grow as we search for sequences of moves.
The branching factor is roughly 35, so at depth 10 we can expect there to be around &lt;span class="math"&gt;\(35^{10} \approx 10^{15}\)&lt;/span&gt; nodes.&lt;/p&gt;
&lt;h2 id="a-simpler-instance"&gt;A simpler instance&lt;/h2&gt;
&lt;p&gt;Let’s examine a simple 2-color instance on a board with 2 rows and 2 columns.
Below is the full search space for such a problem instance.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/nrk_game_1.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 780px;"/&gt;&lt;/p&gt;
&lt;p&gt;The initial node is at the top and the terminal node is at the bottom of the figure.
There are 24 nodes in total.
The game can be solved in 3 or 4 moves, depending on which path we choose.
The original game instance has billions of such nodes, and solving it comes down to searching the graph efficiently.&lt;/p&gt;
&lt;h2 id="reducing-the-search-space"&gt;Reducing the search space&lt;/h2&gt;
&lt;p&gt;The first observation we make is that &lt;strong&gt;a click on any group of connected colors leads to the same node&lt;/strong&gt;.
It does not matter which specific cell we click as long as it’s connected to a group of the same color, so we might as well only consider the top-left cell in any group of connected colors.&lt;/p&gt;
&lt;p&gt;The second observation is that &lt;strong&gt;any permutation of colors represents the same state&lt;/strong&gt;.
If there are &lt;span class="math"&gt;\(4\)&lt;/span&gt; colors, there are &lt;span class="math"&gt;\(4! = 4 \times 3 \times 2 \times 1 = 24\)&lt;/span&gt; ways to permute them.
Applying this observation we can reduce the search space from 24 to 12 nodes in this specific problem instance.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/nrk_game_2.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;The third observation is that &lt;strong&gt;horizontal flips represent the same state&lt;/strong&gt;.
If we’ve solved the game at some node &lt;span class="math"&gt;\(n\)&lt;/span&gt;, then we’ve also solved the node &lt;span class="math"&gt;\(n'\)&lt;/span&gt; with flipped columns.
There are two such states (flipped and not flipped), so this can only reduce the search space by a factor of 2.
By applying this observation we can reduce the search space down to 8 nodes.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/nrk_game_3.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 520px;"/&gt;&lt;/p&gt;
&lt;p&gt;Based on these observations we can define a &lt;em&gt;canonical representation&lt;/em&gt; of each game state (node) where we re-label the colors and flip the game.
There is a tradeoff between:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the computation time spent transforming a game state into canonical form&lt;/li&gt;
&lt;li&gt;the storage of game states as we search the graph&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Re-coloring reduces the size of the search space by quite a bit, so we’ll apply this transformation.
We’ll skip the flipping and full canonicalization.&lt;/p&gt;
&lt;h2 id="optimal-solutions"&gt;Optimal solutions&lt;/h2&gt;
&lt;p&gt;The simplest algorithm that is guaranteed to produce an optimal solution is &lt;a href="https://en.wikipedia.org/wiki/Breadth-first_search"&gt;breadth-first search&lt;/a&gt; (&lt;span class="caps"&gt;BFS&lt;/span&gt;).
It first explores all nodes one move away from the initial node, then all nodes two moves away, then all nodes three nodes away, and so forth.
When we get to an empty board we know that no shorter path exists.&lt;/p&gt;
&lt;p&gt;Unfortunately, &lt;span class="caps"&gt;BFS&lt;/span&gt; holds too many nodes in memory and explores &lt;em&gt;every path&lt;/em&gt;.
A smarter approach is to use &lt;a href="https://en.wikipedia.org/wiki/A%2A_search_algorithm"&gt;A* search&lt;/a&gt;.
All nodes are put in a &lt;a href="https://en.wikipedia.org/wiki/Priority_queue"&gt;priority queue&lt;/a&gt;, and we continually pop off the node with the smallest value of&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
f(n) = g(n) + h(n),
\end{equation*}&lt;/div&gt;
&lt;p&gt;where &lt;span class="math"&gt;\(n\)&lt;/span&gt; is a node, &lt;span class="math"&gt;\(g(n)\)&lt;/span&gt; is the length of the path so far and &lt;span class="math"&gt;\(h(n)\)&lt;/span&gt; is an &lt;em&gt;admissible heuristic&lt;/em&gt; that estimates the total remaining path.&lt;/p&gt;
&lt;h3 id="admissible-heuristics"&gt;Admissible heuristics&lt;/h3&gt;
&lt;p&gt;If the heuristic &lt;span class="math"&gt;\(h\)&lt;/span&gt; never overestimates the length of the remaining path, then A* is guaranteed to return an optimal solution.
Such a heuristic is called admissible.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An obvious admissible &lt;span class="math"&gt;\(h\)&lt;/span&gt; is the unique number of colors left on the board. This never overestimates the number of remaining moves needed, because at least one click is required to remove all cells of a given color.&lt;/li&gt;
&lt;li&gt;An even better &lt;span class="math"&gt;\(h\)&lt;/span&gt; is described in the code below. It’s based on the following idea: if any given color appears on both sides of a column not containing that color, then at least two moves are needed to get rid of that color.&lt;/li&gt;
&lt;/ul&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="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;consecutive_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Count how many consecutive groups of True there are.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; consecutive_groups([False, True, True, False, False, True, True])&lt;/span&gt;
&lt;span class="sd"&gt;    2&lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterable&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;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;a_star_heuristic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&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 lower bound on how many moves are needed to solve.&lt;/span&gt;

&lt;span class="sd"&gt;    This function examines each color in turn. For each color, count whether&lt;/span&gt;
&lt;span class="sd"&gt;    it appears in each column. For each group of columns, separated by&lt;/span&gt;
&lt;span class="sd"&gt;    columns where the color does not appear, we need at least one click.&lt;/span&gt;

&lt;span class="sd"&gt;    Examples&lt;/span&gt;
&lt;span class="sd"&gt;    --------&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; board = Board([[1, 2, 1],&lt;/span&gt;
&lt;span class="sd"&gt;    ...                [2, 2, 2],&lt;/span&gt;
&lt;span class="sd"&gt;    ...                [1, 2, 1]])&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; a_star_heuristic(board)&lt;/span&gt;
&lt;span class="sd"&gt;    3&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; board = Board([[1, 3, 1],&lt;/span&gt;
&lt;span class="sd"&gt;    ...                [2, 3, 1],&lt;/span&gt;
&lt;span class="sd"&gt;    ...                [1, 3, 1]])&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;gt;&amp;gt;&amp;gt; a_star_heuristic(board)&lt;/span&gt;
&lt;span class="sd"&gt;    4&lt;/span&gt;
&lt;span class="sd"&gt;    """&lt;/span&gt;
    &lt;span class="c1"&gt;# Transpose the grid&lt;/span&gt;
    &lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="c1"&gt;# Get the unique integers larger than zero in the matrix&lt;/span&gt;
    &lt;span class="n"&gt;unique_integers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&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="c1"&gt;# For every integer, see if it exists in each column&lt;/span&gt;
    &lt;span class="n"&gt;integer_in_col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;col&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;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matrix&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;unique_integers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Count groups of integers separated by other integers&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consecutive_groups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;integer&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;integer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;integer_in_col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Unfortunately, even with this heuristic A* is too slow for boards of a decent size.
The figure below shows solution times for A* and &lt;span class="caps"&gt;BFS&lt;/span&gt; as a function of board size.
Solution times grow exponentially.
Whether we re-label colors or not seems to have little practical impact on solution times.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/random_boards_to_optimality.png" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"/&gt;&lt;/p&gt;
&lt;h2 id="good-enough-solutions-in-limited-time"&gt;Good-enough solutions in limited time&lt;/h2&gt;
&lt;p&gt;A heuristic graph search algorithm (not to be confused with the heuristic function &lt;span class="math"&gt;\(h\)&lt;/span&gt; in A* search) is an algorithm that attempts to find a good solution.
With heuristics, there are no guarantees that the optimal solution is found.&lt;/p&gt;
&lt;p&gt;Some simple heuristics are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Random search.&lt;/strong&gt; Always choose a random move.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Greedy best-first search.&lt;/strong&gt; Always choose the move that clears the most cells.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Randomized best-first search.&lt;/strong&gt; Choose a move at random, but use weighted sampling and weigh the moves by the number of cells they remove. Setting &lt;code&gt;weight = cells_removed**power&lt;/code&gt; for some &lt;code&gt;power &amp;gt;= 0&lt;/code&gt; is one way to parametrize this graph search. If &lt;code&gt;power = 0&lt;/code&gt; then the search behaves like random search, and if &lt;code&gt;power&lt;/code&gt; is very large then it behaves like greedy best-first search. Setting &lt;code&gt;power=None&lt;/code&gt; means using no randomness.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The figure below shows what happens when we solve the large &lt;span class="math"&gt;\(9 \times 7\)&lt;/span&gt; board 100 times using different values of &lt;code&gt;power.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/randomized_best_first_search.png" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"/&gt;&lt;/p&gt;
&lt;p&gt;Setting &lt;code&gt;power=5&lt;/code&gt; and &lt;code&gt;power=10&lt;/code&gt; both yield solution solution paths with 17 moves.&lt;/p&gt;
&lt;p&gt;We implement three algorithms that yield good-enough solutions quickly.
They are called &lt;strong&gt;anytime beam search&lt;/strong&gt;, &lt;strong&gt;heuristic search&lt;/strong&gt; and &lt;strong&gt;Monte Carlo search&lt;/strong&gt;, and all are more clever than simply repeating randomized best-first search many times.&lt;/p&gt;
&lt;h3 id="anytime-beam-search"&gt;Anytime beam search&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Beam_search"&gt;Beam search&lt;/a&gt; is a simple algorithm:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start at the root, expand all children. Keep only the &lt;code&gt;beam_width&lt;/code&gt; children that are promising, for instance those that have the largest values of &lt;code&gt;cells_cleared / num_moves&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;From the &lt;code&gt;beam_width&lt;/code&gt; nodes under consideration, expand all children. Filter them again and keep only the &lt;code&gt;beam_width&lt;/code&gt; most promising ones.&lt;/li&gt;
&lt;li&gt;Repeat until a solution is found.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One disadvantage of beam search is that for large values of &lt;code&gt;beam_width&lt;/code&gt; we have to wait a while until we get a solution.
An &lt;a href="https://en.wikipedia.org/wiki/Anytime_algorithm"&gt;anytime algorithm&lt;/a&gt; is one that yields good-enough solutions quickly, and if it runs for longer it improves on the solution.
To make beam search into an anytime algorithm we run it for &lt;code&gt;beam_width=1, 2, 4, 8, ...&lt;/code&gt;, keeping track of the best solution seen so far and yielding as better solutions are found.&lt;/p&gt;
&lt;h3 id="heuristic-search"&gt;Heuristic search&lt;/h3&gt;
&lt;p&gt;This algorithm yields better and better solutions as they are found.
If it runs long enough, it will eventually find an optimal solution.
In practice it will run out of memory before it’s guaranteed to explore every path, so there are no guarantees.
It works roughly like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start by running a single deterministic best-first search. This gives a quick upper bound &lt;code&gt;best_solution_length_so_far&lt;/code&gt; on the optimal solution that we can use to prune solutions with later.&lt;/li&gt;
&lt;li&gt;Sort the priority queue by a non-admissible heuristic function, for instance &lt;code&gt;cells_cleared / num_moves&lt;/code&gt;. Always consider moves that maximize this function, hoping they lead to a good solution.&lt;/li&gt;
&lt;li&gt;Let &lt;code&gt;h(n)&lt;/code&gt; be the admissible heuristic function discussed earlier. If &lt;code&gt;num_moves + h(n) &amp;gt;= best_solution_length_so_far&lt;/code&gt;, then we can prune node &lt;code&gt;n&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="monte-carlo-tree-search"&gt;Monte Carlo tree search&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Monte_Carlo_tree_search"&gt;Monte Carlo tree search&lt;/a&gt; also yields better and better solutions as they are discovered.
Like the algorithm above, it also eventually finds an optimal solution but provides no guarantees when run for a short time.
It works roughly like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with a deterministic best-first search to establish an upper bound used for pruning.&lt;/li&gt;
&lt;li&gt;Randomly sample nodes in the explored tree, going down guided by the so-called &lt;em&gt;upper confidence bound&lt;/em&gt;, which trades of exploration and exploitation.&lt;/li&gt;
&lt;li&gt;When a new unexplored node is reached, run a randomized best-first search from the node down to the bottom of the tree to score the node. If this results in a new best solution, update the upper bound. Propagate the score &lt;code&gt;cells_cleared / num_moves&lt;/code&gt; up in the explored tree to the root. This score affects the upper confidence bound used in the next iteration.&lt;/li&gt;
&lt;li&gt;Prune by using the admissible heuristic &lt;code&gt;h(n)&lt;/code&gt; as the tree is explored.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Running all algorithms for around four minutes on the game instance of size &lt;span class="math"&gt;\(9 \times 7\)&lt;/span&gt;, we get the following results.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_16.png" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anytime beam search finds the best-known solution from &lt;span class="caps"&gt;NRK&lt;/span&gt; in two seconds.&lt;/li&gt;
&lt;li&gt;Heuristic search finds a solution of length 17 in around two minutes, but is unable to discover any better solutions in the remaining time.&lt;/li&gt;
&lt;li&gt;Monte Carlo search finds a solution of length 14 in around twenty seconds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the solution sequence for the 12 move solution (click to enlarge).&lt;/p&gt;
&lt;p&gt;&lt;a href="https://tommyodland.com/images/articles/nrk_game/best_solution_found_no_16.png"&gt;
&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/best_solution_found_board_no_16.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 720px;"/&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;NRK&lt;/span&gt; record was 12 moves, and we managed to solve the board in 12 moves.
On this particular instance, running for longer does not lead to further improvements.
On other instances we are not always so lucky; sometimes we are unable to find the best known solution, at least in a few minutes of compute time.&lt;/p&gt;
&lt;h2 id="summary-notes-and-references"&gt;Summary, notes and references&lt;/h2&gt;
&lt;p&gt;The &lt;span class="caps"&gt;NRK&lt;/span&gt; game “Former” can be formulated as a graph search problem, but the graph is huge.
There are billions and billions of nodes in the graph, so complete enumeration is out of the question.
While A* search is effective similar puzzles (e.g. the &lt;a href="https://en.wikipedia.org/wiki/15_puzzle"&gt;15 puzzle&lt;/a&gt;), here it fails because we were unable to design a great admissible heuristic function.&lt;/p&gt;
&lt;p&gt;Heuristic search algorithms are still able to produce good-enough solutions in a short time.
We &lt;a href="https://github.com/tommyod/NRK-former-game"&gt;implemented&lt;/a&gt; three such algorithms with different flavors: &lt;strong&gt;anytime beam search&lt;/strong&gt;, &lt;strong&gt;heuristic search&lt;/strong&gt; and &lt;strong&gt;Monte Carlo search&lt;/strong&gt;.
In the end we managed to find the record solution of 12 moves on this instance.
These algorithms can be combined: heuristic search and Monte Carlo search perform better if an upper bound is established quickly, so running beam search as a first step could help.&lt;/p&gt;
&lt;p&gt;The game is similar to &lt;a href="https://en.wikipedia.org/wiki/SameGame"&gt;SameGame&lt;/a&gt;, and the paper &lt;a href="https://dke.maastrichtuniversity.nl/m.winands/documents/CGSameGame.pdf"&gt;Single-Player Monte-Carlo Tree Search&lt;/a&gt; proposes Monte Carlo search for this game.
An implementation by Odin at &lt;a href="https://github.com/odinhg/nrk-former-sp-mcts"&gt;github.com/odinhg/nrk-former-sp-mcts&lt;/a&gt; implements this algorithm.
Another paper is &lt;a href="https://liacs.leidenuniv.nl/~kosterswa/samegame.pdf"&gt;Solving SameGame and its Chessboard Variant&lt;/a&gt;, which also uses Monte Carlo search.
The &lt;a href="https://github.com/chausner/sgbust"&gt;sgbust&lt;/a&gt; program is a SameGame solver written in C++ that uses beam search.
A good introductory reference for game solving is Chapter 3 “Solving Problems by Searching” in &lt;a href="https://www.amazon.com/Artificial-Intelligence-A-Modern-Approach/dp/0134610997"&gt;Artificial Intelligence: A Modern Approach&lt;/a&gt;.
Another avenue to go down is reinforcement learning, see for instance the book &lt;a href="https://www.amazon.com/Reinforcement-Learning-second-Introduction-Computation-ebook/dp/B08BSYL7R1"&gt;Reinforcement Learning: An Introduction&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to Andreas for introducing me to the game, and to Eivind and Gunvor who independently discovered the admissible heuristic.&lt;/p&gt;
&lt;h2 id="appendix-more-boards"&gt;Appendix: more boards&lt;/h2&gt;
&lt;p&gt;Above we considered the &lt;span class="caps"&gt;NRK&lt;/span&gt; game instance for the 16th of November.
Below we show performance on more game instances from four other days.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_26.png" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_25.png" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_24.png" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/nrk_game/heuristic_searches_board_no_23.png" style="display: block; margin-left: auto; margin-right: auto; width: 90%; max-width: 580px;"/&gt;&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Tue, 26 Nov 2024 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2024-11-26:/articles/2024/solving-nrks-game-former</guid><category>articles</category><category>algorithms</category><category>optimization</category></item><item><title>The average value</title><link>https://tommyodland.com/articles/2024/the-average-value</link><description>&lt;p&gt;Presentation slides and video of a talk I gave about notions of averages (one-parameter models), loss functions and simple machine learning problems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tommyodland.com/files/presentations/avg.pdf"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt; slides from the talk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ZgcBQKFX4dg"&gt;YouTube recording&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" frameborder="0" height="382" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/ZgcBQKFX4dg?si=o4KVi_yBmMhirv9g" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;" title="YouTube video player" width="680"&gt;&lt;/iframe&gt;
&lt;p&gt;Below is a transcript with slides, if you prefer reading over listening.&lt;/p&gt;
&lt;hr/&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-average-value"&gt;The average value&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#table-of-contents"&gt;Table of contents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-pythagorean-averages"&gt;The Pythagorean averages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-p-norm"&gt;The \(p\)-norm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#huber-mellowmax-and-quantiles"&gt;Huber, mellowmax and quantiles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#eta-for-file-downloads"&gt;&lt;span class="caps"&gt;ETA&lt;/span&gt; for file downloads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ranking-products-by-ratings"&gt;Ranking products by ratings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#summary"&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="the-average-value"&gt;The average value&lt;/h2&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-01.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Hello, my name is Tommy and this is a recording of a talk I gave at an &lt;span class="caps"&gt;IT&lt;/span&gt; conference. 
The title is “&lt;em&gt;The average value&lt;/em&gt;” and the intended audience is intermediate-level data scientists.
In this recording, I’ll go through the presentation informally.
I’ll assume you have some mathematical background and are interested in data science.
If you want to look at these slides or check the references, you can download them from &lt;a href="https://tommyodland.com/files/presentations/avg.pdf"&gt;this &lt;span class="caps"&gt;URL&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-02.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;The average value—this is the equation for it, and if that was all there was to say, the talk would end here. 
This slide introduces some notation, but obviously to make a presentation out of this, we need to generalize away from this equation.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-03.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;We’ll do that by looking at the optimization problem that this equation—the arithmetic average—solves.
That’s the least squares optimization problem. 
In general, in machine learning, when we do regression tasks, it’s very common that we have squared loss and some kind of function &lt;span class="math"&gt;\(f\)&lt;/span&gt; where we want to compute the optimal parameters for &lt;span class="math"&gt;\(f\)&lt;/span&gt;. Here, &lt;span class="math"&gt;\(f\)&lt;/span&gt; could be a tree-based model, linear regression, or neural network, and somehow we find the best parameters &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;However, there is typically no closed-form solution.
For the arithmetic average there is a closed-form equation, but for many more complicated models, you use something like gradient descent or Newton’s method.
You end up with a result, but there’s no equation you can use to just compute the results—it’s an iterative procedure.
In many problems, there’s no guarantee that you have a global minimum or that you always end up with the same one.&lt;/p&gt;
&lt;p&gt;Basically, this talk isn’t really about the average value—&lt;em&gt;it’s about setting the model &lt;span class="math"&gt;\(f\)&lt;/span&gt; to be a single parameter model&lt;/em&gt;.
We set &lt;span class="math"&gt;\(f(x,\theta) = \theta\)&lt;/span&gt; and ask ourselves “what else can we do?”.
If we fix the model to be the simplest possible model we can think of, what else can we do? 
We can tweak the loss function!&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of contents&lt;/h2&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-04.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Here’s the table of contents for the presentation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, we’ll look at the Pythagorean averages—the arithmetic average, geometric average, and harmonic average—and see that there are loss functions that induce these averages in a very natural way&lt;/li&gt;
&lt;li&gt;Then we’ll look at the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm, which you might know about—it’s a way to measure distance, and we can use that to get the notion of the median and the midpoint&lt;/li&gt;
&lt;li&gt;We’ll examine Huber, Mellox, and quantiles, which are one-parameter models that go further away from the Pythagorean averages and &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm&lt;/li&gt;
&lt;li&gt;Finally, we’ll look at some real-world examples aimed at developers: (1) &lt;span class="caps"&gt;ETA&lt;/span&gt; for file downloads and (2) Ranking products by ratings on websites&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-pythagorean-averages"&gt;The Pythagorean averages&lt;/h2&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-05.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Let’s start with the Pythagorean averages.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-06.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;The arithmetic average is the most common average, and it minimizes squared loss.
There’s an assumption that if you’re twice as far off, it’s four times worse because you’re squaring the residuals.
It has really nice mathematical properties—basically, if you have squared loss, you have a quadratic, so when you differentiate it, you get something linear that you can solve immediately.
Newton’s method can solve this in one iteration—you can one-shot these squared problems!&lt;/p&gt;
&lt;p&gt;This figure is important to understand—it shows for each data point (1, 3, and 6) the loss, and then shows the sum of these three losses over the three data points.
That’s the solid blue line, and this is the loss function.
If you minimize it, you get the arithmetic average, shown in the dashed line.&lt;/p&gt;
&lt;p&gt;One final comment in the footnote: how do we get this squared loss in the first place?
One way is to look at the normal distribution, which is essentially an exponential to the power of squares.
But you also end up with averages in Poisson models and binomial models, so there are many types of assumptions about data that lead to arithmetic averages.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-07.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;The geometric average minimizes multiplicative loss.
If you take the logarithm of the data and the logarithm of the model and compare those, you get the multiplicative loss.
Here, being one order of magnitude below is equally bad as being one order of magnitude above.
The geometric average is like the arithmetic average but with multiplication—it’s the number that you have to multiply with itself &lt;span class="math"&gt;\(n\)&lt;/span&gt; times to get the product of the data.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-08.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;The harmonic average minimizes relative loss, where you measure errors relative to the true value.
This is equivalent to doing weighted least squares where the weights are given by &lt;span class="math"&gt;\(1/y_i\)&lt;/span&gt;.
This particular loss—the relative loss—is not the only loss function that induces this notion of average.
The loss in the footnotes will also lead to the same harmonic average, but the relative loss one is more natural and commonly used.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-09.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Let’s look at these three loss functions in a different problem setting.
Suppose you have resources to distribute—maybe you have bakeries and predictions for tomorrow’s bread sales.
You have a central bakery where you bake bread and want to distribute it to cover every store.&lt;/p&gt;
&lt;p&gt;Unfortunately, you don’t have enough bread to cover every store, so you need to distribute according to some rule.
You would like your distribution &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; to be as close as possible to the demand of each store &lt;span class="math"&gt;\(y\)&lt;/span&gt;, so you use a loss function.
Depending on which loss function you use, you get three different distributions.&lt;/p&gt;
&lt;p&gt;I’m not sure what the correct answer would be—it would probably depend on the bakeries’ goals.
Would you use squared loss, multiplicative loss, or relative loss in the real world?
I’ll leave that up to you to think about.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-10.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;We’ve seen several loss functions, and it might be interesting to discuss how you would choose between them.
Every loss function has the property that if your prediction is very close to the target, it’s good, and if it’s far off, it’s bad.&lt;/p&gt;
&lt;p&gt;How do you choose between loss functions? 
One thing I like to do is consider the null space—by that, I mean considering which types of errors are equally bad and whether that makes sense in your application. 
For instance, if the true values &lt;span class="math"&gt;\(y\)&lt;/span&gt; are 2 and 8: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Squared errors would imply that going two up in the positive direction on either prediction is equally bad&lt;/li&gt;
&lt;li&gt;Multiplicative errors mean that predicting 4 (8/2) or 16 (8*2) is equally bad&lt;/li&gt;
&lt;li&gt;Relative errors mean that subtracting 2 from the first prediction or adding 8 to the second one is equally bad, because those represent equal units of true values&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here are some practical examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you’re trying to predict medicine dosages, you probably want to use relative or multiplicative errors, since different drugs work on different dosage scales&lt;/li&gt;
&lt;li&gt;If you’re predicting economic outcomes or investments, you probably don’t want to use multiplicative or relative errors because money isn’t relative—one unit of money is the same whether it comes from a small project or a huge project&lt;/li&gt;
&lt;li&gt;For financial predictions, you might want to use absolute errors rather than squared errors, because being twice as far off means losing twice as much, not four times as much (unless you want to consider the utility of money, but let’s skip that for now)&lt;/li&gt;
&lt;li&gt;When people look at interest rates or summarize computer performance benchmarks (CPUs, etc.), they almost always use the geometric average&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-p-norm"&gt;The &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm&lt;/h2&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-11.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Having finished the first part about Pythagorean averages, let’s talk about the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm. &lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-12.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;We’re going to generalize this equation in a different direction by solving this optimization problem for different values of &lt;span class="math"&gt;\(p\)&lt;/span&gt; instead of &lt;span class="math"&gt;\(p=2\)&lt;/span&gt; (which gives us the arithmetic average).&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-13.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;If we set &lt;span class="math"&gt;\(p\)&lt;/span&gt; equal to one, we get the absolute error.
This is an absolute value function placed on each data point, and when you sum it up, it’s piecewise linear.
If there’s an odd number of data points, there’s a unique maximum; if there’s an even number of data points, it’s flat between the two middle points—there’s no unique minimum.&lt;/p&gt;
&lt;p&gt;The median has an interesting property: if I were to change the data point 6 and move it up to 8 or down to 4, as long as it doesn’t cross 3, the median stays the same.
The median as a function completely ignores the location of every data point except for the middle one.
Obviously, if 6 crosses 3, then something happens with the function, but it’s essentially blind to many of the data points.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-14.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Choosing &lt;span class="math"&gt;\(p=1.5\)&lt;/span&gt; is one of many ways to create a function between a median and an average.
It’s also the first optimization problem we’ve seen where there is no closed-form solution—here you would use an iterative optimization routine to find the minimum.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-15.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Choosing &lt;span class="math"&gt;\(p=2\)&lt;/span&gt; is the squared error, which we’ve seen before.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-16.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;As you increase &lt;span class="math"&gt;\(p\)&lt;/span&gt; further, the functions become more and more sharp.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-17.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;You get to an interesting property in the limit as &lt;span class="math"&gt;\(p\)&lt;/span&gt; becomes very large: minimizing the &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm becomes equivalent to minimizing the maximum.
If you want to minimize the maximum distance, you want to make the worst case as good as possible, and you do that by placing yourself at the midpoint.
This is one of the measures of centrality you might have learned in high school—the midpoint.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-18.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;How can we combine these Pythagorean losses and &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norms?
Let’s put them in a table:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mean absolute error&lt;/li&gt;
&lt;li&gt;Mean squared error&lt;/li&gt;
&lt;li&gt;Maximum error (not used much in machine learning but used in engineering where you want to optimize the worst case, like in airplane design)&lt;/li&gt;
&lt;li&gt;Mean squared logarithmic error&lt;/li&gt;
&lt;li&gt;Mean absolute percentage error&lt;/li&gt;
&lt;li&gt;Weighted mean squared error&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some combinations aren’t listed but could be imagined.
For instance, with &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; and multiplicative error, you’d get something like the mean absolute logarithmic error.
The point is that you can construct and combine these functions.&lt;/p&gt;
&lt;p&gt;How would you do that?
You can first look at an individual data point and consider what kind of loss function you’re interested in, then use a &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm to combine them. The &lt;span class="math"&gt;\(p\)&lt;/span&gt;-norm is a way to combine the individual losses.&lt;/p&gt;
&lt;p&gt;Think about what properties you want in your application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With absolute error, residuals of &lt;span class="math"&gt;\((4,0)\)&lt;/span&gt; are as bad as &lt;span class="math"&gt;\((3,1)\)&lt;/span&gt; and &lt;span class="math"&gt;\((2,2)\)&lt;/span&gt;. With money, this makes sense—if you’re off by four, you probably don’t care how it’s distributed if it’s money lost&lt;/li&gt;
&lt;li&gt;It also means that &lt;span class="math"&gt;\((9,0)\)&lt;/span&gt; is better than &lt;span class="math"&gt;\((5,5)\)&lt;/span&gt;, even though errors in &lt;span class="math"&gt;\((5,5)\)&lt;/span&gt; are more balanced&lt;/li&gt;
&lt;li&gt;Maximum error just looks at the largest value, so &lt;span class="math"&gt;\((9,0)\)&lt;/span&gt; would be as bad as &lt;span class="math"&gt;\((9,5)\)&lt;/span&gt; and &lt;span class="math"&gt;\((9,9)\)&lt;/span&gt;, while &lt;span class="math"&gt;\((8,8)\)&lt;/span&gt; would be better than &lt;span class="math"&gt;\((9,0)\)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="huber-mellowmax-and-quantiles"&gt;Huber, mellowmax and quantiles&lt;/h2&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-19.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Now let’s look at even more esoteric notions of averages, maxima, minima, and everything in between.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-20.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;One issue with squared errors is that they can be overly sensitive to outliers.
One remedy for this is to use the smooth Huber loss, which acts like a square when you’re close to zero but like the absolute value when you zoom out.
This makes it less sensitive to outliers while still maintaining a unique minimum even with an even number of data points.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-21.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;There’s an opposite problem that might occur in some applications: squared errors are very flat around zero, so if you have a small residual and you square it, it becomes even smaller.
If you’re regularizing coefficients (like the &lt;span class="caps"&gt;LASSO&lt;/span&gt; does) and want to drive them all the way to zero, it’s very nice to have something that acts like the absolute value close to zero.
The reverse Huber has this effect—it’s like the absolute value when you’re close to zero and acts like a square if you’re far away.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-22.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Mellowmax and mellowmin are smooth generalizations of maximum and minimum.
They are functions that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt; goes to infinity, the function approaches the maximum&lt;/li&gt;
&lt;li&gt;As &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt; goes to negative infinity, the function approaches the minimum&lt;/li&gt;
&lt;li&gt;Around zero, the function approaches the arithmetic average&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want something that acts in between the arithmetic average and the maximum, mellowmax is a nice way to do it.
The maximum only depends on the maximum value in the dataset, but sometimes when you want to minimize the maximum, you want to bring all values down.
One way to do that is to drive down the maximum value, but as a function, the maximum is blind to every other value apart from the largest one.
If you use mellowmax instead, it’s actually a bit sensitive to other values, which can better inform the optimization routine.&lt;/p&gt;
&lt;p&gt;Sometimes it’s very nice to relax these functions that are piecewise linear or have derivatives that are zero for most inputs.
The median only depends on one or two values for most inputs, and if you want something more smooth, this is a nice option.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-27.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Let’s quickly cover quantiles—I assume you know about them.
They’re a way to be somewhere between the median and the minimum/maximum.
They put an absolute value function on every data point but scale it up and down on the left and right.&lt;/p&gt;
&lt;p&gt;A small generalization: there’s no reason we have to use the absolute value—we could use squared residuals too. If &lt;span class="math"&gt;\(p=2\)&lt;/span&gt;, you would call it expectile, but in general, you don’t &lt;em&gt;have&lt;/em&gt; to use &lt;span class="math"&gt;\(p=1\)&lt;/span&gt; or &lt;span class="math"&gt;\(p=2\)&lt;/span&gt;.
You could in principle use any value for &lt;span class="math"&gt;\(p\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="eta-for-file-downloads"&gt;&lt;span class="caps"&gt;ETA&lt;/span&gt; for file downloads&lt;/h2&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-28.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Let’s look at our first practical example: &lt;span class="caps"&gt;ETA&lt;/span&gt; for file downloads. &lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-29.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Suppose you’re a developer writing a routine that will download files, copy files, or install software.
The computer is doing chunks of work, and you have the time that each chunk takes.
You know the amount of work done and how much work will be done in total (like the total megabytes for all files to download).
You want to estimate how long this will take.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-30.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;To solve this, we need to handle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Non-equidistant arrivals (times or pieces of work)&lt;/li&gt;
&lt;li&gt;We’ll assume a linear model is reasonable (parameters can change—if someone else starts downloading, your speed might drop, but after it drops we assume it’s still linear with a different rate)&lt;/li&gt;
&lt;li&gt;Very importantly, when a new data point arrives, we must be able to update our model in constant time—we can’t iterate over all previous data points, as there could be millions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-32.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Here’s one approach: we can compute the average rate of work. 
With two pieces of work (two files downloaded), we can compute the slope for each piece (work done over time elapsed). 
By minimizing this function, we get &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;, which is the average amount of work done per time.&lt;/p&gt;
&lt;p&gt;I weight this by &lt;span class="math"&gt;\(\Delta t\)&lt;/span&gt; (time) because when I solve that optimization problem, it leads to an equation with nice symmetry in &lt;span class="math"&gt;\(\theta\)&lt;/span&gt;.
It should make intuitive sense that downloading a file in 1 second contains some information, but downloading the next file in 30 seconds contains much more information—so large &lt;span class="math"&gt;\(\Delta t\)&lt;/span&gt; should be weighted more heavily.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-31.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;We use &lt;span class="math"&gt;\(w_i\)&lt;/span&gt; to weigh the most recent data more heavily. 
We can split it up like this: the most recent data point is attached at the end, so we have the previous estimate and some new data. 
We can quickly update by keeping track of our previous computation and adding to it.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-32.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;We then take a linear model constrained to go through the last data point seen.
We have one degree of freedom left, which we use on the slope with this estimate.
We set the weights proportional to some number to the power of &lt;span class="math"&gt;\(i\)&lt;/span&gt; over &lt;span class="math"&gt;\(\gamma\)&lt;/span&gt;, where &lt;span class="math"&gt;\(\gamma\)&lt;/span&gt; is the half-life.
This parameterized the weights so the most recent data is weighted more heavily.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-36.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Here’s how it performs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the speed changes when we see more data, obviously it’s not a very good model&lt;/li&gt;
&lt;li&gt;If it’s linear all the way through, it’s quite accurate&lt;/li&gt;
&lt;li&gt;With some deviation from linearity, it picks up an average rate somewhere in between&lt;/li&gt;
&lt;li&gt;With noise, it goes through the last data point and picks up an average rate that’s quite close&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As more data arrives, it will actually improve because it’ll pick up on new regimes with different rates of change.
This is a simple algorithm—very fast and accurate in many real-world cases.
It depends on the application, but it’s probably more advanced than many file &lt;span class="caps"&gt;ETA&lt;/span&gt; estimates I’ve seen, which tend to be jittery and jump around too much.&lt;/p&gt;
&lt;h2 id="ranking-products-by-ratings"&gt;Ranking products by ratings&lt;/h2&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-38.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Our final example is ranking products by ratings.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-39.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;This is a fascinating real-world example of how you can use averages effectively.
I looked at Norwegian e-commerce websites where users can give ratings.
All these websites use a &lt;strong&gt;naive average rating&lt;/strong&gt;, which produces some strange results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The average of a single 5 is 5, but the average of many 5s and a few 4s would be slightly less than 5&lt;/li&gt;
&lt;li&gt;This means a product with one 5-star rating would rank higher than a product with hundreds of 5-star ratings and a few 4-star ratings&lt;/li&gt;
&lt;li&gt;I would trust the product with more ratings to be better, as one rating doesn’t give much certainty about true quality&lt;/li&gt;
&lt;li&gt;Many websites also place products with no ratings at the very bottom, which I think is wrong—a new product with no ratings is likely average, not the worst product&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I didn’t find any Norwegian e-commerce websites that do this correctly (I didn’t look at international ones).
One of the few sites that gets this right is the IMDb Top 250 list—they account for the number of ratings and use an equation similar to what we’re going to deduce.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-40.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;We want our average rating to have two properties:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It should take into account the number of ratings&lt;/li&gt;
&lt;li&gt;A product without any ratings is likely typical, not really bad&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here’s a crucial idea: what are we really doing when we’re trying to rank products?
&lt;strong&gt;We’re going to frame this as a prediction problem—we’re predicting the next rating.&lt;/strong&gt;
If you were to buy this product and rate it, what would your rating be?
That would be the next rating for this product.&lt;/p&gt;
&lt;p&gt;So we’re casting it as a prediction problem and saying that ranking products is equivalent to predicting the next rating and sorting by that prediction.
We’ll do this by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Introducing a prior average &lt;span class="math"&gt;\(\bar{y}\)&lt;/span&gt; and a pseudo count &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Solving this optimization problem&lt;/li&gt;
&lt;li&gt;The solution shows the meaning of the pseudo count—it’s added to &lt;span class="math"&gt;\(n\)&lt;/span&gt; as dummy ratings, and the value of these dummy ratings is &lt;span class="math"&gt;\(\bar{y}\)&lt;/span&gt; (the prior average rating)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-41.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;To determine the hyperparameters &lt;span class="math"&gt;\(\bar{y}\)&lt;/span&gt; and &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt;, we need to do cross-validation.
I assume you know about leave-one-out and k-fold cross-validation.
We’re going to do “leave-next-out” cross-validation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s very important that your cross-validation procedure be as similar as possible to how you want to use the model.&lt;/strong&gt;
Since we defined our notion of ranking as predicting the next value, we’ll simulate that exactly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with no ratings, try to predict the first one&lt;/li&gt;
&lt;li&gt;Get to know the first rating, look at the squared residual&lt;/li&gt;
&lt;li&gt;Use that first data point to predict the second one&lt;/li&gt;
&lt;li&gt;Get to know the second data point and compare&lt;/li&gt;
&lt;li&gt;Continue this process&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is essentially a time series split where you learn one more data point at a time.
Both of these can be computed in linear time for these simple models, so we can do this very quickly.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-42.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Let’s apply it to a real dataset.
There’s a Norwegian website called Legelisten that ranks doctors.&lt;/p&gt;
&lt;p&gt;I found 17 doctors with 212 total ratings.
I used our average with some weights, parameterizing those weights by the half-life &lt;span class="math"&gt;\(\bar{y}\)&lt;/span&gt;.
Using leave-next-out cross-validation, I found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Optimal number of pseudo-ratings is around &lt;span class="math"&gt;\(\alpha=2\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Optimal half-life is around &lt;span class="math"&gt;\(\gamma =7\)&lt;/span&gt; years&lt;/li&gt;
&lt;li&gt;Prior rating is &lt;span class="math"&gt;\(\bar{y}=3.8\)&lt;/span&gt; (on a scale from 1 to 5)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The loss when using the arithmetic average as our predictor was 1.77.
With this regularized temporal average, it’s 1.55—a very nice reduction in loss that leads to better predictions of the next rating.&lt;/p&gt;
&lt;p&gt;You might ask why not make this into a big machine learning project using all kinds of covariates, but I don’t think people would like that.
If you rank doctors using information about their gender and age, it’s probably not good.
This solution is something people will understand and won’t feel discriminates unfairly based on age, gender, or other factors.
It’s still super simple, just more nuanced and created from thinking through the problem more carefully.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-43.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Looking at the results (blue is the old website rating, green is my proposed rating):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The third doctor: One person rated this doctor with a 5. The old ranking puts them first; we say no—one rating isn’t much, so we pull them down. The doctor with many 5s deserves to be ranked higher.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The tenth doctor is interesting because they’re pulled away from the grand average. Typically without temporal weighting, you pull towards the grand average, but this doctor has gotten more positive reviews lately—lots of 5s and 4s, 44 ratings in total. This doctor has improved over the years, so the half-life is forgiving the older low ratings and pulling up towards the more recent 5s.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The seventeenth doctor hasn’t been rated once. The old ranking would place them last; we say no—with no ratings, we believe you’re in the middle of the pack. We place them there, certainly better than someone with just many 1-star ratings.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-44.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;p&gt;Let’s summarize the key points.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded img-responsive" src="https://tommyodland.com/images/articles/edc2024/the-average-value-slide-45.png" style="display: block; margin-left: auto; margin-right: auto; width: 100%; max-width: 680px;"/&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Only simple problems have closed-form solutions.&lt;/strong&gt; Don’t worry so much about how to solve things when you start modeling—look at what properties you want to model instead. Solving is left to an optimization algorithm.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There’s a good reason why people like squared errors&lt;/strong&gt;—they have nice mathematical properties. Not everything I showed today can be solved efficiently for bigger models.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In machine learning, the solution depends on both the model and the loss function.&lt;/strong&gt; Many data scientists focus heavily on the model, discussing which is better in detail. While the model is important, choosing the wrong loss function can be just as problematic. For instance, if errors are truly relative in your application but you choose &lt;span class="caps"&gt;RMSE&lt;/span&gt;, or you have an economic application but use the wrong loss function, you’re probably modeling the whole thing incorrectly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adapt your approach to the problem.&lt;/strong&gt; Combine existing ideas but don’t reinvent—there are many good books and sources. People have thought about amazing things, so read up on that. If you find yourself inventing something you believe is totally new to solve a practical problem, you’re probably reinventing something—do a literature search.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Always start simple.&lt;/strong&gt; Never use models you don’t understand—you’ll have a hard time explaining them to others, and they might behave in ways you don’t fully understand. There’s still plenty of room to have fun with simple models. Even today, some e-commerce sites are billion-dollar businesses and don’t implement averages correctly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The final comment I want to make is that if you go back to the first slide and remove the restriction of the one-parameter model, everything we talked about still holds.
All the loss functions and modeling approaches are still relevant, but by choosing one-parameter models, I was able to create nice plots and talk about things simply.
Many of these concepts are still relevant for other models, even though computationally it’s not always easy to optimize.&lt;/p&gt;
&lt;p&gt;I really hope you enjoyed my presentation.
Thanks a lot for watching, and again, you can download the slides and look at the references if you want to.
Thank you.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Sun, 10 Nov 2024 00:00:00 +0100</pubDate><guid>tag:tommyodland.com,2024-11-10:/articles/2024/the-average-value</guid><category>articles</category><category>datascience</category></item><item><title>Smooth taxes without brackets</title><link>https://tommyodland.com/articles/2024/smooth-taxes-without-brackets</link><description>&lt;p&gt;I recently implemented a calculator for Norwegian income tax, and it was harder than I anticipated.
&lt;strong&gt;The implementation required around 30 lines of code and 20 constants&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2024_marginal.png" style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"/&gt;&lt;/p&gt;
&lt;p&gt;In this article we use the term “income tax” to refer to everything the government deducts from gross income.
To compute income tax, one must take into account the &lt;a href="https://www.skatteetaten.no/en/rates/bracket-tax/"&gt;brackets&lt;/a&gt;, the &lt;a href="https://www.skatteetaten.no/en/rates/national-insurance-contributions/"&gt;national insurance contributions&lt;/a&gt;, the &lt;a href="https://www.skatteetaten.no/en/rates/minimum-standard-deduction/"&gt;minimum standard deduction&lt;/a&gt;, the &lt;a href="https://www.skatteetaten.no/en/rates/personal-allowance/"&gt;personal allowance&lt;/a&gt;, and so forth.&lt;/p&gt;
&lt;p&gt;In this article we’ll simplify the income tax calculation to &lt;strong&gt;one line of code and two constants&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We’ll start by looking empirically at the Norwegian income tax, then we’ll study properties that a tax scheme should have.
In the end we’ll construct a smooth tax function with two adjustable parameters &lt;span class="math"&gt;\(H\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt;:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = 
\frac{H(e^{-kx} + kx - 1)}{k}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
We’ll show that this function has good properties and that for suitable values of &lt;span class="math"&gt;\(H\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt; it approximates the current income tax very well.
Below is a figure showing the average tax rate and the marginal tax rate induced by the function &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; above.
You’ll understand this figure better as you read the next sections.
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2024_smooth.png" style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"/&gt;&lt;/p&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;h3 id="the-tax-function-tx"&gt;The tax function &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Let &lt;span class="math"&gt;\(T: \mathbb{R}_+ \to \mathbb{R}_+\)&lt;/span&gt; be the income tax as a function of gross income &lt;span class="math"&gt;\(x\)&lt;/span&gt;.
For instance, if Alice earns a gross income of &lt;span class="math"&gt;\(x=100\)&lt;/span&gt; units of money, then she might have to pay &lt;span class="math"&gt;\(T(100) = 20\)&lt;/span&gt; in taxes, leaving her with a net income of &lt;span class="math"&gt;\(x - T(x) = 100 - 20 = 80\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;If we have such a function &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;, then we can deduce three other interesting functions that answer specific questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The net income &lt;span class="math"&gt;\(x - T(x)\)&lt;/span&gt;: “How much money do I have left after paying taxes?”&lt;/li&gt;
&lt;li&gt;The average tax rate &lt;span class="math"&gt;\(T(x) / x\)&lt;/span&gt;: “What percentage of my total income do I pay in taxes?”&lt;/li&gt;
&lt;li&gt;The marginal tax rate &lt;span class="math"&gt;\(T'(x) \approx T(x+1) - T(x)\)&lt;/span&gt;: “If I earn one more unit of money, what fraction of that unit of money goes to taxes?”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="proportional-and-progressive-tax"&gt;Proportional and progressive tax&lt;/h3&gt;
&lt;p&gt;A tax scheme is called &lt;em&gt;proportional&lt;/em&gt; if the tax rate is a fixed percentage.
In our notation, this would mean that &lt;span class="math"&gt;\(T(x) = cx\)&lt;/span&gt; for some constant &lt;span class="math"&gt;\(c\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;A tax scheme is called &lt;em&gt;progressive&lt;/em&gt; if those who earn more pay a higher percentage in taxes.
In addition to progressive tax schemes, we can define &lt;em&gt;non-regressive&lt;/em&gt; and &lt;em&gt;strictly progressive&lt;/em&gt; tax schemes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Non-regressive&lt;/strong&gt;: the average tax rate &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; never decreases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progessive&lt;/strong&gt;: the average tax rate &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; never decreases, and it &lt;em&gt;increases at least once&lt;/em&gt; in the domain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strictly progressive&lt;/strong&gt;: the average tax rate &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; always increases.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mathematically, these requirements correspond to conditions on the derivatives of the average tax rate.
For instance, a non-regressive tax function &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; has the constraint that
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align}
\label{progressive} \tag{1}
\left( \frac{T(x)}{x} \right)' \geq 0 \Rightarrow T'(x) - \frac{T(x)}{x} \geq 0.
\end{align}&lt;/div&gt;
&lt;p&gt;
The second inequality comes from applying the product rule of differentiation.
Having established some notation and concepts that we’ll use later, we will now empirically examine the Norwegian income tax.&lt;/p&gt;
&lt;h2 id="the-norwegian-income-tax"&gt;The Norwegian income tax&lt;/h2&gt;
&lt;p&gt;The Norwegian income tax has several deductions, a few if-then-else rules and a (mostly) increasing marginal tax rate.
Once all the rules are implemented and combined, the result can be visualized as a function.
The implementation was tested against the calculator &lt;a href="https://www.smartepenger.no/kalkulatorer/3786-skatteberegning-2024"&gt;Skatteberegning 2024&lt;/a&gt;, and in the background the income distribution from 2022 is shown.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2024.png" style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"/&gt;&lt;/p&gt;
&lt;p&gt;The jumps on the marginal tax rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; make the figure a little disorderly, but notice that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s progressive, since the average tax rate &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; never decreases and it does increase.&lt;/li&gt;
&lt;li&gt;It’s not strictly progressive because &lt;span class="math"&gt;\(T(x)/x\)&lt;/span&gt; has two flat regions.&lt;/li&gt;
&lt;li&gt;The limit evaluates to &lt;span class="math"&gt;\(\lim_{x \to \infty} T(x) / x = 0.474\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;The marginal tax rate is discontinuous, and actually jumps down once.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The government often changes the effective tax rates: they adjust deductions, the lower and upper limits on the brackets and rates within each bracket.&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2023_2024.png" style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"/&gt;&lt;/p&gt;
&lt;p&gt;To get an idea of how erratic these changes are, consider the highest bracket that extends to infinity.
In 2020 the lower limit of the highest bracket was 999 550, then in 2021 it was raised to 1 021 550. It doubled to 2 000 000 in 2022, was reduced by half a million to 1 500 000 in 2023 and cut further to 1 350 001 in 2024.&lt;/p&gt;
&lt;h2 id="properties-that-smooth-taxes-should-have"&gt;Properties that smooth taxes should have&lt;/h2&gt;
&lt;p&gt;To implement the Norwegian income tax I had to read up on tax rules from several sources online.
I also had to test the implementation against an online calculator multiple times before I got it right.
&lt;strong&gt;Wouldn’t it be nice if &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; was just a simple formula?&lt;/strong&gt;
No jumps and kinks—just a smooth, simple function.&lt;/p&gt;
&lt;p&gt;For instance, given a constant &lt;span class="math"&gt;\(k &amp;gt;0\)&lt;/span&gt;, what if &lt;span class="math"&gt;\(T(x) = x(1 - e^{-xk})\)&lt;/span&gt;?
That way the average tax rate becomes &lt;span class="math"&gt;\(T(x)/x = 1 - e^{-xk}\)&lt;/span&gt;, which seems reasonable… right?&lt;/p&gt;
&lt;p&gt;It turns out that this is a seriously bad idea, as we can see in this figure:
&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/taxes/exponential_tax.png" style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:700px;"/&gt;&lt;/p&gt;
&lt;p&gt;The net income &lt;span class="math"&gt;\(x - T(x) = x - x(1 - e^{-xk})\)&lt;/span&gt; is maximized at &lt;span class="math"&gt;\(x^{\star} = 1/k\)&lt;/span&gt;.
There is no incentive to earn more money than this, since you &lt;em&gt;lose money by earning more&lt;/em&gt;.
This is a terrible property for a tax system to have—back to the drawing board!&lt;/p&gt;
&lt;h3 id="property-1-good-incentives"&gt;Property 1: Good incentives&lt;/h3&gt;
&lt;p&gt;Let’s set a simple rule: earning more should always mean keeping more after taxes.
In other words, if we increase our gross income from &lt;span class="math"&gt;\(x\)&lt;/span&gt; to &lt;span class="math"&gt;\(x + \epsilon\)&lt;/span&gt;, then we should end up with more net income after paying taxes:&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\underbrace{(x + \epsilon) - T(x + \epsilon)}_{\text{net income with $\epsilon$ more}} &amp;gt; \underbrace{x - T(x)}_{\text{net income}}
\end{align*}&lt;/div&gt;
&lt;p&gt;Rearranging this expression we find that the condition is equivalent to
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\frac{T(x + \epsilon) - T(x)}{\epsilon} &amp;lt; 1,
\end{align*}&lt;/div&gt;
&lt;p&gt;
and in the limit &lt;span class="math"&gt;\(\epsilon \to 0\)&lt;/span&gt; the requirement says that the marginal tax rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; must obey &lt;span class="math"&gt;\(T'(x) &amp;lt; 1\)&lt;/span&gt;.
If this inequality is satisfied, then higher pre-tax income always leads to higher post-tax income.
In other words, there’s always an incentive to increase one’s gross income &lt;span class="math"&gt;\(x\)&lt;/span&gt;.&lt;/p&gt;
&lt;h3 id="property-2-progressive-taxation"&gt;Property 2: Progressive taxation&lt;/h3&gt;
&lt;p&gt;Another natural constraint to impose is that &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; should be non-decreasing, since then the tax scheme must be non-regressive.
To see why this is true, note that if &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; is non-decreasing and non-negative, then &lt;span class="math"&gt;\(T(x) = \int_0^{x} T'(\tau) \, d \tau \leq x T'(x)\)&lt;/span&gt;.
Rearranging this inequality gives &lt;span class="math"&gt;\(T'(x) \geq T(x)/ x\)&lt;/span&gt;, which is an inequality that we have seen before in Equation \eqref{progressive} in the introduction.
It is exactly what is needed for a tax scheme to be non-regressive.&lt;/p&gt;
&lt;p&gt;Note that the converse is not true: a tax scheme can be non-regressive even if &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; decreases.
For instance, the Norwegian marginal tax does decrease at one point, but the tax is still non-regressive overall. If &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; is not only non-decreasing, but also &lt;em&gt;increases&lt;/em&gt; at some point, then the resulting tax scheme is guaranteed to be progressive.&lt;/p&gt;
&lt;p&gt;Due to these constraints on the marginal tax rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt;, it’s easier to find good functions &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; if we start with &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; and integrate to obtain &lt;span class="math"&gt;\(T(x) = \int_0^{x} T'(\tau) \, d \tau\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2 id="constructing-tax-functions"&gt;Constructing tax functions&lt;/h2&gt;
&lt;p&gt;Recall that the marginal tax rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; says how much we’ll be taxed on the next unit of money earned.
For instance, &lt;span class="math"&gt;\(T'(x=100 \, 000) = 0.25\)&lt;/span&gt; in the Norwegian income tax, so if we earn &lt;span class="math"&gt;\(100\, 000\)&lt;/span&gt; &lt;span class="caps"&gt;NOK&lt;/span&gt; the next unit of money will be taxed by &lt;span class="math"&gt;\(0.25\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Proposing a function.&lt;/strong&gt;
Let’s again consider the function &lt;span class="math"&gt;\(1 - e^{-xk}\)&lt;/span&gt;, but this time as the marginal tax rate &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt; instead of as an average tax rate:
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T'(x) = 
1 - e^{-xk}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
This function satisfies both our properties: it’s always less than one, and it never decreases.
If we integrate it we obtain
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = \int_0^{x} T'(\tau) \, d \tau = \int_0^{x} (1 - e^{-\tau k}) \, d \tau
=
\frac{e^{-kx} - 1}{k} + x.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Notice that our proposed marginal tax rate &lt;span class="math"&gt;\(1 - e^{-xk}\)&lt;/span&gt; is zero when &lt;span class="math"&gt;\(x=0\)&lt;/span&gt; and approaches one as &lt;span class="math"&gt;\(x\to \infty\)&lt;/span&gt;.
The interpretation is that there is no tax initially as one starts to earn income, but if one approaches infinite income then units of money earned toward infinity will be taxed more and more.&lt;/p&gt;
&lt;p&gt;Generalizing ever so slightly, we can change the marginal tax rate to &lt;span class="math"&gt;\(T'(x) = H - (H-L)e^{-k x}\)&lt;/span&gt;.
This way the marginal tax starts at a low value &lt;span class="math"&gt;\(L\)&lt;/span&gt; and converges to a high value &lt;span class="math"&gt;\(H\)&lt;/span&gt;.
Integrating, we obtain the tax function
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x) = \int_0^{x} T'(\tau) \, d \tau = \int_0^{x} H - (H-L)e^{-k \tau} \, d \tau
=
\frac{(H-L) e^{-kx} + H(kx - 1) + L}{k}.
\end{align*}&lt;/div&gt;
&lt;p&gt;
The current policy today in Norway is that the first units of money earned are not taxed.
Therefore, let us set &lt;span class="math"&gt;\(L=0\)&lt;/span&gt; and optimize for &lt;span class="math"&gt;\(H\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt; by curve fitting to the Norwegian tax rate:&lt;/p&gt;
&lt;p&gt;&lt;img class="img-fluid rounded" src="https://tommyodland.com/images/articles/taxes/norwegian_income_tax_2024_exponential_interpolated.png" style="display: block; margin-left: auto; margin-right: auto; width: 95%; max-width:600px;"/&gt;&lt;/p&gt;
&lt;p&gt;The smooth model based on &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt; is shown in dashed lines.
The error is never more than 5300 &lt;span class="caps"&gt;NOK&lt;/span&gt;, so this approximation is pretty good.
It’s easy to implement if you want to play around with it in Excel or Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;numpy&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="nn"&gt;np&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;T&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;"""Norwegian income tax for 2024. &lt;/span&gt;
&lt;span class="sd"&gt;    Max error &amp;lt; 5300 NOK, mean absolute error ~1400."""&lt;/span&gt;
    &lt;span class="n"&gt;H&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="mf"&gt;0.4772&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.8745e-06&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&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="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k&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;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;k&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Income tax computation is quite complex.
To implement it one needs around 30 lines of code with deductions, rules and tax brackets, all defined using around 20 constants.&lt;/p&gt;
&lt;p&gt;In this post we examined smooth tax functions &lt;span class="math"&gt;\(T(x)\)&lt;/span&gt;. One such function is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T(x; H, k) = 
\frac{H(e^{-kx} + kx - 1)}{k},
\end{align*}&lt;/div&gt;
&lt;p&gt;
which mimics the overall behavoir of the current income tax calculation with good accuracy.&lt;/p&gt;
&lt;p&gt;Maybe it could be an alternative to today’s system?
After all, a single formula is easy to implement compared to the current rule set with various deductions, constants and tax brackets.
Everyone has access to the exponential function on their phones or computers.
A simple function makes it harder for politicians to obfuscate the tax system by introducing new rules and conditions.
They could still modify the tax rate by modifying constants such as &lt;span class="math"&gt;\(H\)&lt;/span&gt; and &lt;span class="math"&gt;\(k\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;One argument against a smooth function is that the average person does not understand the mathematics of it.
This might be true, but then again: the average person probably does not understand what a bracketed marginal tax system is either.&lt;/p&gt;
&lt;h3 id="keep-it-simple"&gt;Keep it simple!&lt;/h3&gt;
&lt;p&gt;If I was a benevolent dictator, I would:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simplify tax formulas and other computations performed by the government as much as possible&lt;/li&gt;
&lt;li&gt;Favor simple, smooth functions (2-3 parameters) over discretizations and bracketing&lt;/li&gt;
&lt;li&gt;Require government agencies that use formulas to publish them in math and code, with tests&lt;/li&gt;
&lt;li&gt;Ensure that properties such as good incentives are baked into the models&lt;/li&gt;
&lt;li&gt;Run an exhaustive test suite to guarantee good incentives across all combinations of gross income and all manner of government payouts, ensuring that citizens are never punished for earning more and working more (this &lt;a href="https://www.nav.no/no/nav-og-samfunn/kunnskap/analyser-fra-nav/nyheter/1-av-4-dagpengemottakere-kan-tape-pa-a-jobbe-en-dag"&gt;does happen&lt;/a&gt;, and it might be a sign of overly complex systems that have not been thought through carefully)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="notes-and-references"&gt;Notes and references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Continuous tax brackets were discussed on the &lt;a href="https://money.stackexchange.com/questions/125745/why-is-income-tax-generally-not-in-a-continuous-spectrum"&gt;Personal Finance &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; Money Stack Exchange&lt;/a&gt; back in 2020.&lt;/li&gt;
&lt;li&gt;The paper &lt;a href="https://docs.iza.org/dp11493.pdf"&gt;Smooth Income Tax Schedules: Derivation and Consequences&lt;/a&gt; by Schwarz et al discusses how various countries approach taxation and propose using smooth functions.&lt;/li&gt;
&lt;li&gt;A Quora post with the fantastic title &lt;a href="https://ajydvfgtczonwiqu.quora.com/The-US-Federal-Tax-Code-is-More-Complicated-Than-Calculus-So-Lets-Use-Calculus"&gt;The &lt;span class="caps"&gt;US&lt;/span&gt; Federal Tax Code is More Complicated Than Calculus. So Let’s Use Calculus&lt;/a&gt; also looks at smooth functions. The author proposes shifting and scaling a function of the form &lt;span class="math"&gt;\(T'(x) = 1 - 1/(x + 1)\)&lt;/span&gt;, whereas we shifted and scaled &lt;span class="math"&gt;\(T'(x) = 1 - e^{-x}\)&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;Any sensible cumulative probability density function can be used to construct &lt;span class="math"&gt;\(T'(x)\)&lt;/span&gt;, offering a flexible approach to designing smooth tax functions. I chose the exponential function because it’s relatively simple to work with.&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Wed, 16 Oct 2024 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2024-10-16:/articles/2024/smooth-taxes-without-brackets</guid><category>articles</category><category>mathematics</category></item><item><title>Arithmetic coding in Python</title><link>https://tommyodland.com/articles/2024/arithmetic-coding-in-python</link><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Arithmetic_coding"&gt;Arithmetic coding&lt;/a&gt; is a lossless data compression algorithm.
It encodes a sequence of symbols into a sequence of bits.
After reading the 1987 paper “&lt;a href="https://doi.org/10.1145/214762.214771"&gt;Arithmetic coding for data compression&lt;/a&gt;” I decided to implement the algorithm in clean, modern Python.&lt;/p&gt;
&lt;p&gt;In this article I’ll demonstrate how to use my Python implementation, how well arithmetic coding works when the symbol frequency is skewed, and how to encode “Crime and Punishment.”
We also compare arithmetic coding with the more well-known &lt;a href="https://en.wikipedia.org/wiki/Huffman_coding"&gt;Huffman coding&lt;/a&gt;.
The implementation is available at: &lt;a href="https://github.com/tommyod/arithmetic-coding"&gt;github.com/tommyod/arithmetic-coding&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="a-simple-example"&gt;A simple example&lt;/h2&gt;
&lt;p&gt;Here we create a &lt;code&gt;message&lt;/code&gt; to encode and put an End Of Message (&lt;span class="caps"&gt;EOM&lt;/span&gt;) symbol &lt;code&gt;"&amp;lt;EOM&amp;gt;"&lt;/code&gt; at the end of it.
We also need to set up a table of symbol frequencies.
Arithmetic coding exploits the frequent symbols and assigns fewer bits to them, though unlike Huffman encoding arithmetic encoding does not actually use a fixed bit sequence per symbol.&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;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;arithmetic_coding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;EOM&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;
&lt;span class="go"&gt;[1, 1, 0, 0, 0, 0, 1, 1]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can decode these bits back to symbols and we get the original message back.&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;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;['A', 'B', 'A', 'A', 'A', '&amp;lt;EOM&amp;gt;']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This examples encodes the message to 8 bits.
A naive approach would be to assign a unique 2-bit sequence to each symbol, e.g., &lt;code&gt;{"A": 0b00, "B": 0b01, "&amp;lt;EOM&amp;gt;": 0b10}&lt;/code&gt;.
Since the message contains 6 symbols, we would need &lt;code&gt;6 * 2 = 12&lt;/code&gt; bits with this naive approach.&lt;/p&gt;
&lt;h2 id="infrequent-symbols"&gt;Infrequent symbols&lt;/h2&gt;
&lt;p&gt;To see how the encoder exploits the frequency of symbols, consider this example.
We initialize &lt;code&gt;ArithmeticEncoder(bits=32)&lt;/code&gt; because we need more internal bits in the buffer of the encoder to represent low-frequency symbols.&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;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"A"&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="s2"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;EOM&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;43&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Amazingly, a message with a million symbols is encoded to only 43 bits!
Having seen how efficient arithmetic coding is with skewed frequencies, let’s apply it to a real-world example.&lt;/p&gt;
&lt;h2 id="encoding-crime-and-punishment"&gt;Encoding “Crime and Punishment”&lt;/h2&gt;
&lt;p&gt;Let’s download the English translation of “&lt;a href="https://www.gutenberg.org/ebooks/2554"&gt;Crime and Punishment&lt;/a&gt;” by Fyodor Dostoevsky.&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;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;string&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://www.gutenberg.org/cache/epub/2554/pg2554.txt"&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printable&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbols&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="s2"&gt;"&amp;lt;EOM&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;1164281&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For this example, we’ll use the actual frequencies from the text, though in practice one might use a more general frequency table for the English language.&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;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;83&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"q"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="go"&gt;(104735, 766)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="naive-encoding"&gt;Naive encoding&lt;/h3&gt;
&lt;p&gt;The Python code above shows that there are 83 distinct printable symbols in the book. Since &lt;span class="math"&gt;\(2^7 = 128\)&lt;/span&gt;, a naive approach would be to use seven bits for each symbol, regardless of its frequency.
We would then encode &lt;code&gt;"A"&lt;/code&gt; to &lt;code&gt;0b0000000&lt;/code&gt;, &lt;code&gt;"B"&lt;/code&gt; to &lt;code&gt;0b0000001&lt;/code&gt;, &lt;code&gt;"C"&lt;/code&gt; to &lt;code&gt;0b0000010&lt;/code&gt; and so forth.
This approach would encode the book using &lt;code&gt;1164281 * 7 = 8149967&lt;/code&gt; bits.&lt;/p&gt;
&lt;h3 id="arithmetic-encoding"&gt;Arithmetic encoding&lt;/h3&gt;
&lt;p&gt;Instead of using the naive approach, the arithmetic encoder exploits that symbols like &lt;code&gt;"e"&lt;/code&gt; are much more frequent than symbols like &lt;code&gt;"q"&lt;/code&gt;.
In around three seconds we’re able to encode all the 1.05 million symbols in the book:&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;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;5259420&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The result has around 5.26 million bits, and the bits/symbol ratio is 4.52.&lt;/p&gt;
&lt;h3 id="shannon-entropy-as-a-lower-bound-on-bits"&gt;Shannon entropy as a lower bound on bits&lt;/h3&gt;
&lt;p&gt;The Shannon entropy, which is a lower bound on how many bits we need on average to encode a message of this length with these frequencies, is
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation*}
-\sum_i f_i \log_2 \left( p_i \right),
\end{equation*}&lt;/div&gt;
&lt;p&gt;
where &lt;span class="math"&gt;\(f_i\)&lt;/span&gt; is the frequency (count) of symbol &lt;span class="math"&gt;\(i\)&lt;/span&gt; and &lt;span class="math"&gt;\(p_i = f_i / \sum_j f_j\)&lt;/span&gt; is the probability of symbol &lt;span class="math"&gt;\(i\)&lt;/span&gt;.
The bound only applies if we assume that each symbol is independent of the others—if we assume higher-order statistics, higher compression ratios might be possible.&lt;/p&gt;
&lt;p&gt;Computing this value for “Crime and Punishment”, we find that the best we can hope for is&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;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;probs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&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;total&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&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="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;shannon_bound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;probs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; 
&lt;span class="gp"&gt;... &lt;/span&gt;                    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;shannon_bound&lt;/span&gt;
&lt;span class="go"&gt;5259418.898940693&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="huffman-encoding"&gt;Huffman encoding&lt;/h3&gt;
&lt;p&gt;We also use the &lt;a href="https://github.com/soxofaan/dahuffman"&gt;dahuffman&lt;/a&gt; Python library to produce a Huffman encoding, which maps each symbol to a unique code value of variable length.
Notice how common symbols like &lt;code&gt;"e"&lt;/code&gt; are mapped to short code values, while rare symbols like &lt;code&gt;"B"&lt;/code&gt; correspond to long code values.&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;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;dahuffman&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HuffmanCodec&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;codec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;HuffmanCodec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_frequencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frequencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print_code_table&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;Bits Code                    Value Symbol&lt;/span&gt;
&lt;span class="go"&gt;   3 000                         0 'e'&lt;/span&gt;
&lt;span class="go"&gt;   4 0010                        2 'h'&lt;/span&gt;
&lt;span class="go"&gt;   4 0011                        3 'i'&lt;/span&gt;
&lt;span class="go"&gt;   5 01000                       8 'u'&lt;/span&gt;
&lt;span class="go"&gt;   9 010010000                 144 'R'&lt;/span&gt;
&lt;span class="go"&gt;  10 0100100010                290 'Y'&lt;/span&gt;
&lt;span class="go"&gt;  10 0100100011                291 'B'&lt;/span&gt;
&lt;span class="go"&gt;  ...&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;symbol_to_bitsize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;bitsize&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bitsize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;                     &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;codec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_code_table&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol_to_bitsize&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;symbol&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;symbol&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;5298110&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Huffman encodes the book to around 5.3 million bits.&lt;/p&gt;
&lt;h3 id="arithmetic-coder-with-an-adaptive-model"&gt;Arithmetic coder with an adaptive model&lt;/h3&gt;
&lt;p&gt;If we do not want to assume known frequencies, we can use a simple adaptive model.
The frequency count for each symbol is initialized to one.
As the encoder processes symbols it increments the counts to update the probability model.
The decoder mimics this process exactly, but in reverse.
We still need to know all distinct symbols that the encoder and decoder expects to see beforehand though (unless we iterate over the data twice).&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;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArithmeticEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;5260166&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bits&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
&lt;span class="go"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="a-comparison-table"&gt;A comparison table&lt;/h3&gt;
&lt;p&gt;Comparing all the methods we’ve mentioned, we get the following table.&lt;/p&gt;
&lt;table class="table-striped table-hover table"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Compression Method              &lt;/th&gt;
&lt;th style="text-align: left;"&gt;Bits Used    &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Naive encoding (7 bits/symbol)  &lt;/td&gt;
&lt;td style="text-align: left;"&gt;8,149,967    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Huffman encoding                &lt;/td&gt;
&lt;td style="text-align: left;"&gt;5,298,110    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Arithmetic encoding (adaptive)  &lt;/td&gt;
&lt;td style="text-align: left;"&gt;5,260,166    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Arithmetic encoding (fixed)    &lt;/td&gt;
&lt;td style="text-align: left;"&gt;5,259,420    &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Shannon entropy bound          &lt;/td&gt;
&lt;td style="text-align: left;"&gt;5,259,418.9  &lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Naively encoding each symbol using a fixed bit length is wasteful, since it does not account for the fact that some symbols are much more frequent than others.
Huffman encoding improves greatly upon this, but arithmetic encoding is even better and is able to get within two bits of the Shannon bound.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Arithmetic coding maps a sequence of symbols into a sequence of bits.
Unlike Huffman coding, there is no one-to-one correspondence between a single symbol and a single bit sequence.
Both methods exploit the fact that some symbols are typically more common than others.&lt;/p&gt;
&lt;p&gt;With arithmetic coding there are two types of probability models: fixed and adaptive.
A fixed model uses a static frequency count, computed by either looping the data in advance or by observing a larger corpus of text.
A dynamic model starts out with equal probabilities for all symbols, and adapts to the specific message as it iterates through the symbols.
Regardless of which model is used, the logic of the decoder must exactly match the logic of the encoder.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Tommy Odland</dc:creator><pubDate>Thu, 19 Sep 2024 00:00:00 +0200</pubDate><guid>tag:tommyodland.com,2024-09-19:/articles/2024/arithmetic-coding-in-python</guid><category>articles</category><category>algorithms</category></item></channel></rss>