<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Experimentalist: Data Metroscopy]]></title><description><![CDATA[Measure what matters. Dive into data engineering and observability where capturing, managing, and analyzing measurements of the world is the point of it all.]]></description><link>https://substack.the-experimentalist.com/s/data-metroscopy</link><image><url>https://substackcdn.com/image/fetch/$s_!0yut!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba6cba4c-0a9a-477b-a6d1-af81556e2fa5_1280x1280.png</url><title>The Experimentalist: Data Metroscopy</title><link>https://substack.the-experimentalist.com/s/data-metroscopy</link></image><generator>Substack</generator><lastBuildDate>Wed, 08 Apr 2026 07:52:55 GMT</lastBuildDate><atom:link href="https://substack.the-experimentalist.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[CodeKami Consulting LLC]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[rmpinchback@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[rmpinchback@substack.com]]></itunes:email><itunes:name><![CDATA[Reid M. Pinchback]]></itunes:name></itunes:owner><itunes:author><![CDATA[Reid M. Pinchback]]></itunes:author><googleplay:owner><![CDATA[rmpinchback@substack.com]]></googleplay:owner><googleplay:email><![CDATA[rmpinchback@substack.com]]></googleplay:email><googleplay:author><![CDATA[Reid M. Pinchback]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Markov Chain Convergence in Python]]></title><description><![CDATA[When does a Markov Chain &#8216;forget&#8217; its starting point?]]></description><link>https://substack.the-experimentalist.com/p/markov-chain-convergence-in-python</link><guid isPermaLink="false">https://substack.the-experimentalist.com/p/markov-chain-convergence-in-python</guid><dc:creator><![CDATA[Reid M. Pinchback]]></dc:creator><pubDate>Thu, 28 Aug 2025 17:20:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9IAn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9IAn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9IAn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9IAn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9IAn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9IAn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9IAn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg" width="514" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:514,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:480639,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9IAn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9IAn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9IAn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9IAn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff7238830-b58e-4dce-b1b2-198eff500d95_514x400.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There are two direct benefits to training a Markov Chain on data. You can:</p><ol><li><p>Analyze the structure of the model to understand the qualities of the data.</p></li><li><p>Generate new data that is very similar to the training data.</p></li></ol><p>The focus of this article series is more the second point, but the first can help with the second. Trying to generate data raises some questions.</p><ul><li><p>How do you know if the data generated actually is a reasonable facsimile of the training data?</p></li><li><p>Are there any inputs to the process, other than the training data, that could influence whether that generated data is a reasonable facsimile?</p></li><li><p>Much like with sampling in the field of statistics, how do you know when you have enough data from a Markov Chain?</p></li></ul><div><hr></div><p><strong>Prev: <a href="https://substack.the-experimentalist.com/p/markov-chains-with-networkx-and-pydtmc">Markov Chains with NetworkX and PyDTMC</a></strong></p><div><hr></div><p>Generating data from a Markov Chain requires an initial state to begin the process, and a random number each time we take a step. To cover these questions we will:</p><ol><li><p>Create runs of output data. These are sequences of states.</p></li><li><p>Analyze the probability distribution of the states in those runs.</p></li><li><p>Make analysis repeatable by controlling random numbers via seeds.</p></li><li><p>Examine sensitivity of the runs to the choice of seed used.</p></li><li><p>Use all the possible initial states as initial states.</p></li><li><p>Assess how long it takes the model to &#8220;forget&#8221; the state it started with when generating output. We want it to forget because the choice of initial state is somewhat &#8212; later we&#8217;ll get into why I said &#8220;somewhat&#8221; &#8212; arbitrary, and we don&#8217;t want to wire a persistent bias into the generated data.</p></li></ol><p>Some of this can be examined empirically by doing data-generation experiments and analyzing the results. Some can be estimated analytically via known techniques for Markov Chains.</p><p>We&#8217;ll be doing both. The empirical can help explain the purpose of the analytical.</p><h2>Taxi Trips as a DTFMC, Revisited</h2><p>Once again, here is our adjacency matrix that we use to build a Discrete-Time Finite Markov Chain.</p><pre><code>adj_rows = dict(
    airport= [  0, 0.2, 0.7, 0.1],
    hospital=[0.4,   0, 0.6,   0],
    hotel=   [0.4, 0.2,   0, 0.4],
    mall=    [0.2, 0.1, 0.7,   0],
)
adj_matrix = pd.DataFrame.from_dict(adj_rows,
                                    orient='index',
                                    dtype=float,
                                    columns=list(adj_rows.keys()))
model = mc.MarkovChain(adj_matrix)</code></pre><p>From this model, we can use the analysis tools of the chain to get the stationary distribution, which is the theoretical long-running distribution of states that would appear in chains. We&#8217;ll wrap it in a nicer DataFrame to match the adjacency matrix:</p><pre><code>data = {
  # In PyDTMC 'stationary_distributions' is an alias of 'pi';
  # You'll see pi symbols in mathematical treatments of Markov Chains
  'probability': model.pi[0]
}
distribution: pd.DataFrame = pd.DataFrame(data=data,
                                          index=adj_matrix.index.copy())
distribution.index.name = 'state'</code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!l83L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!l83L!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png 424w, https://substackcdn.com/image/fetch/$s_!l83L!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png 848w, https://substackcdn.com/image/fetch/$s_!l83L!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png 1272w, https://substackcdn.com/image/fetch/$s_!l83L!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!l83L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png" width="315" height="211" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:211,&quot;width&quot;:315,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9982,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!l83L!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png 424w, https://substackcdn.com/image/fetch/$s_!l83L!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png 848w, https://substackcdn.com/image/fetch/$s_!l83L!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png 1272w, https://substackcdn.com/image/fetch/$s_!l83L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e7f18d7-4bcc-408f-a52a-8cb2d7b42aa3_315x211.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>We can visualize this as a histogram:</p><pre><code>distribution.plot(kind='bar', color='mediumorchid')</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rBD6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rBD6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png 424w, https://substackcdn.com/image/fetch/$s_!rBD6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png 848w, https://substackcdn.com/image/fetch/$s_!rBD6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png 1272w, https://substackcdn.com/image/fetch/$s_!rBD6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rBD6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png" width="593" height="483" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e2b26c52-5207-41da-af14-49a946caef0b_593x483.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:483,&quot;width&quot;:593,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:15985,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rBD6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png 424w, https://substackcdn.com/image/fetch/$s_!rBD6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png 848w, https://substackcdn.com/image/fetch/$s_!rBD6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png 1272w, https://substackcdn.com/image/fetch/$s_!rBD6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2b26c52-5207-41da-af14-49a946caef0b_593x483.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Going back to our earlier questions:</p><ol><li><p><em>&#8220;How do you know if the data generated actually is a reasonable facsimile of the training data?&#8221;</em> The answer revolves around deciding if our generated data has a distribution similar enough to the stationary distribution. The generated data should have a histogram roughly like what we see above.</p></li><li><p><em>&#8220;Are there any inputs to the process, other than the training data, that could influence whether that generated data is a reasonable facsimile?&#8221;</em> Here the answer will be determining if the initial state or the seed or the number of steps of progress cause the generated data to not look like the stationary distribution.</p></li><li><p><em>&#8220;How do you know when you have enough data from a Markov Chain?&#8221;</em> It is possible that some of the concerns of the second question above go away when enough data is generated. We will examine how much is enough for us to see approximately the correct histogram.</p></li></ol><h2>Lining Up The Pieces</h2><p><strong>Piece 1:</strong> <em>&#8220;Create runs of output data. These are sequences of states.&#8221;</em></p><p>PyDTMC gives us a starting point for this. It takes a little tidying to map between state names and state numbers (row offsets in the adjacency matrix):</p><pre><code>from_state_mapping =\
    {state: index_pos for index_pos, state in enumerate(adj_matrix.index)}
from_index_pos_mapping =\
    {index_pos: state for index_pos, state in enumerate(adj_matrix.index)}
simulated = model.simulate(steps=5,
                           initial_state=from_state_mapping['hotel'],
                           seed=42,
                           output_indices=True)
states = [from_index_pos_mapping[index_pos] for index_pos in simulated]</code></pre><p>That gets us:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w-pN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w-pN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png 424w, https://substackcdn.com/image/fetch/$s_!w-pN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png 848w, https://substackcdn.com/image/fetch/$s_!w-pN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png 1272w, https://substackcdn.com/image/fetch/$s_!w-pN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w-pN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png" width="554" height="30" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:30,&quot;width&quot;:554,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2339,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w-pN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png 424w, https://substackcdn.com/image/fetch/$s_!w-pN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png 848w, https://substackcdn.com/image/fetch/$s_!w-pN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png 1272w, https://substackcdn.com/image/fetch/$s_!w-pN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdde7cd95-a351-4a6f-ac12-b9df87e4189c_554x30.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The result is our initial state, <code>'hotel'</code>, plus the 5 randomly-generated next states that we asked for.</p><p>We will build on that and create a Pandas DataFrame suited to how we will analyze generated runs.</p><p><strong>Piece 2:</strong> <em>&#8220;Analyze the probability distribution of the states in those runs.&#8221;</em></p><p>Ok, this one takes a little more coding work. We need to:</p><ul><li><p>Generate the simulated run.</p></li><li><p>Treat each value in that run as a DataFrame row corresponding to a step.</p></li><li><p>Convert the string name of the step into separate column, so <code>'airport'</code> is counted in a different column than <code>'mall'</code>; a marker value of 1 means present, 0 means absent.</p></li><li><p>Make running aggregates at each step, so that at step 3 we have the per-state aggregate from the initial state + step 1 + step 2 + step 3.</p></li><li><p>Use those aggregates to generate the probability distribution implied at that step. This means adding the aggregates to get a total number of all states seen, then dividing the aggregates by the total.</p></li></ul><p>Here we go!</p><pre><code># 'states' are from the simulated run above
running_df = pd.DataFrame({'state': states})
running_df.index.name = 'step'
# count how many of each state we used as of each step in the process
state_count_df = pd.get_dummies(data=running_df, 
                                prefix='', 
                                prefix_sep='').cumsum()
# add any missing states because we may not have used them all yet
# and cumsum would drop missing states from the result
model_states = adj_matrix.index.tolist()
missing_columns = set(model_states) - set(state_count_df.columns)
if missing_columns:
    for state in missing_columns:
        state_count_df[state] = 0
        # debugging is easier it we keep the state column-order canonical
        state_count_df = state_count_df[self.model_states]
# add the count aggregates to our running log
running_df = pd.concat([running_df, state_count_df], axis=1)
# turn the current row into its implied distribution
distribution_df = state_count_df.div(state_count_df.sum(axis=1), axis=0)
distributions = [row.to_frame() for _, row in distribution_df.iterrows()]
for distribution in distributions:
    # make it look like the structure of the stationary distribution
    distribution.index.name = 'state'
    distribution.rename(columns={0:'probability'}, inplace=True)
running_df['distribution'] = distributions</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GkPE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GkPE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png 424w, https://substackcdn.com/image/fetch/$s_!GkPE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png 848w, https://substackcdn.com/image/fetch/$s_!GkPE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png 1272w, https://substackcdn.com/image/fetch/$s_!GkPE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GkPE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png" width="1023" height="275" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:275,&quot;width&quot;:1023,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27424,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GkPE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png 424w, https://substackcdn.com/image/fetch/$s_!GkPE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png 848w, https://substackcdn.com/image/fetch/$s_!GkPE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png 1272w, https://substackcdn.com/image/fetch/$s_!GkPE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F176c98b0-455e-4d05-bfd7-c0579432f540_1023x275.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The outcome is a running total of the appearances of each state. The <code>distribution</code> column looks a little messy but that is because each value is itself an entire DataFrame object, which will come in handy later. </p><p><strong>Piece 3:</strong> <em>&#8220;Make analysis repeatable by controlling random numbers via seeds.&#8221;</em></p><p>We&#8217;ll be doing a decent number of runs where we want to extract the signal of what we are attempting from the surrounding noise of randomness. Because of that I decided to use random numbers generated by the Python <code>secrets</code> module so that I would be tapping whatever entropy backed <code>/dev/urandom</code>. The seeds are then saved to a Parquet file for re-use.</p><pre><code>class SeedHandler:
    
    def __init__(self, *, num_seeds, bits_per_seed, seed_location):
        self.num_seeds = bits_per_seed
        self.bits_per_seed = bits_per_seed
        self_seed_location = seed_location

    @property
    def seeds(self):
        if not os.path.exists(self.seed_location):
            seed_values = self._make_seeds()
            self._write_seeds(seed_values=seed_values)
        else:
            seed_values = self._read_seeds()
        return seed_values

    def _make_seeds(self):
        values = []
        for i in range(self.num_seeds):
            value = secrets.randbits(self.bits_per_seed)
            values.append(value)
        return tuple(values)

    def _read_seeds(self):
        seed_df = pd.read_parquet(path=self.seed_location,
                                  engine='pyarrow')
        seed_ds = seed_df.astype(dtype=dict(seed=self.numpy_dtype))['seed']
        return tuple(seed_ds)

    def _write_seeds(self, *, seed_values):
        seed_ds = pd.Series(data=seed_values, name='seed')
        seed_df = seed_ds.to_frame()
        seed_df.to_parquet(path=self.seed_location, engine='pyarrow')</code></pre><p>There&#8217;s <a href="https://gitlab.com/The-Experimentalist/article_support_2025/-/blob/main/article/2025-08-28-markov-chain-convergence-in-python/convergence.ipynb">more code in the Jupyter notebook</a>, but the above shows the essence.</p><p><strong>Piece 4:</strong> <em>&#8220;Examine sensitivity of the runs to the choice of seed used.&#8221;</em></p><p>The resulting seeds will be supplied to every call of <code>simulate()</code>, which will be handled within the function that generates the states and builds <code>running_df</code>. We just need to augment that DataFrame with a <code>'seed'</code> column.</p><pre><code><code>for seed in seeds:
    running_df =\
        running_distributions(steps=steps,
                              initial_state=initial_state,
                              seed=seed)
        # get a 'step' column from the existing index
        running_df.reset_index(drop=False, inplace=True)
        # and add the seed
        running_df['seed'] = seed</code></code></pre><p>That gets us two out of three pieces of what will become our multi-index.</p><p><strong>Piece 5:</strong> <em>&#8220;Use all the possible initial states as initial states.&#8221;</em></p><p>And now we get our third piece of the multi-index. We need another loop around the code above so we can supply different choices of <code>initial_state</code>, add a column to track it, build an index from the three parts, and <code>pd.concat()</code> the <code>running_df</code> from each (<code>initial_state</code>, <code>seed</code>) combination.</p><pre><code>index_columns = ['initial_state', 'step', 'seed']
across_states_and_seeds_df = None
for initial_state in model_states:
    for seed in seeds:
        running_df =\
            running_distributions(steps=steps,
                                  initial_state=initial_state,
                                  seed=seed)
            running_df.reset_index(drop=False, inplace=True)
            running_df['initial_state'] = initial_state
            running_df['seed'] = seed
            # create the multi-index
            running_df.set_index(index_columns, drop=True, inplace=True)
            if across_states_and_seeds_df is None:
                across_states_and_seeds_df = running_df
            else:
                across_states_and_seeds_df =\
                    pd.concat([across_states_and_seeds_df, running_df],
                              axis=0)</code></pre><p><strong>Piece 6:</strong> <em>&#8220;Assess how long it takes the model to forget the state it started with.&#8221;</em></p><p>This will be examined in two different ways.</p><ol><li><p>Analytically.</p></li><li><p>From the data.</p></li></ol><p>The analytical part is supplied by a PyDTMC model property:</p><pre><code>model.mixing_rate</code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4nOC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4nOC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png 424w, https://substackcdn.com/image/fetch/$s_!4nOC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png 848w, https://substackcdn.com/image/fetch/$s_!4nOC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png 1272w, https://substackcdn.com/image/fetch/$s_!4nOC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4nOC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png" width="179" height="30" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:30,&quot;width&quot;:179,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2681,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4nOC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png 424w, https://substackcdn.com/image/fetch/$s_!4nOC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png 848w, https://substackcdn.com/image/fetch/$s_!4nOC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png 1272w, https://substackcdn.com/image/fetch/$s_!4nOC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F781fc41f-3ad3-4750-bfc0-1dbe47f8eeca_179x30.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>I believe this property is misnamed. The implementation appears to be what is usually called the &#8220;relaxation rate&#8221; or &#8220;convergence rate&#8221;. This is the rate at which deviations from the stationary distribution shrink as more steps are added to the chain generated. It&#8217;s derived from the same eigenvalue information as the spectral gap.</p><p>To get an estimate of how many steps that implies &#8212; the mixing time &#8212; before a desired amount of convergence is achieved, we would have to adjust for how close we want the generated distribution to be to the stationary one.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!F49k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!F49k!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png 424w, https://substackcdn.com/image/fetch/$s_!F49k!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png 848w, https://substackcdn.com/image/fetch/$s_!F49k!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png 1272w, https://substackcdn.com/image/fetch/$s_!F49k!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!F49k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png" width="169" height="66" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:66,&quot;width&quot;:169,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2457,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!F49k!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png 424w, https://substackcdn.com/image/fetch/$s_!F49k!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png 848w, https://substackcdn.com/image/fetch/$s_!F49k!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png 1272w, https://substackcdn.com/image/fetch/$s_!F49k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c5d0581-6162-42c8-a6f5-a4dc285717e9_169x66.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>This is a bit opaque since we aren&#8217;t really indicating what we are measuring via that rate, but as a first approximation we&#8217;ll use <code>epsilon=0.001</code>:</p><pre><code>epsilon = 0.001
taxi_structure.model.mixing_rate * math.log(1 / epsilon)</code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-nWq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-nWq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png 424w, https://substackcdn.com/image/fetch/$s_!-nWq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png 848w, https://substackcdn.com/image/fetch/$s_!-nWq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png 1272w, https://substackcdn.com/image/fetch/$s_!-nWq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-nWq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png" width="166" height="35" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:35,&quot;width&quot;:166,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1691,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-nWq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png 424w, https://substackcdn.com/image/fetch/$s_!-nWq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png 848w, https://substackcdn.com/image/fetch/$s_!-nWq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png 1272w, https://substackcdn.com/image/fetch/$s_!-nWq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2756ba70-0720-4f95-9575-7d127b3ef745_166x35.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>That&#8217;s our analytical reference point, we want to see how the chain distributions look when we have the 19th step after the initial state.</p><p>The reason I picked an epsilon that small is because we haven&#8217;t discussed what kind of distance should matter. I did some digging via the PyDTMC source code, Wikipedia, and several Monte Carlo texts to get an answer.</p><ul><li><p>If I were to use the PyDTMC <strong>mixing_time()</strong> method I believe it uses the square root of the dot product between the stationary distribution and the distribution of the generated chain.</p></li><li><p>The more usual mathematical definition is based on the greatest absolute difference between the two distributions, called the &#8220;Total Variation Distance&#8221; (TVD). It&#8217;s definitely the one I find discussed in material on convergence.</p></li></ul><p>The second is closer to what we want, but not quite. We care about:</p><ul><li><p>Average closeness: what PyDTMC provides.</p></li><li><p>Worst-case closeness: what TVD provides.</p></li><li><p>Similarity of shape between the two distributions: which neither provide.</p></li></ul><p>We want all three, and getting the third with a small value gets you the first two.</p><p>As a reminder of what we&#8217;re trying to achieve in this piece, we want to know when the choice of initial state no longer has a meaningful bias on the distribution of the chain. By comparing the distribution of the generated chain to the stationary distribution, when they are similar enough we are declaring that there is no longer any level of bias from the initial state that we would care about.</p><p>For this reason I added a different measure of closeness to the code, based upon the Jensen-Shannon Distance (JSD). It is histogram-friendly by being symmetric (comparing A to B is the same as comparing B to A), and robust to empty bins:</p><pre><code>eps: float = 1e-9
adjusted_sample = (sample + eps) / (sample + eps).sum()
distance = sp_sd.jensenshannon(p=stationary_distribution,
                               q=adjusted_sample)[0]</code></pre><p>The adjustment shown deals with a limitation in JSD when the two distributions get very close and you&#8217;re faced with the limit of dividing a small value by another value that is approaching zero.</p><p>There is a bit more code to getting the distance behaving when faced with numerical error, <a href="https://gitlab.com/The-Experimentalist/article_support_2025/-/blob/main/article/2025-08-28-markov-chain-convergence-in-python/convergence.ipynb">which is in the Jupyter notebook</a>. It&#8217;s a little bit of a hack but the alternative would be to implement JSD from scratch to deal with a special case where numerical error triggered sqrt(negative number) instead of sqrt(zero). I found in practice a small number of random seeds tripped over that case when generating large sequences.</p><h2>Assembling the Pieces</h2><p>Now that we have a notion of distance, we need to add it to our computation of the running results.</p><p>We will want to examine that distance at each step and see if it is mostly declining, and what our measure is at the 19th step we derived analytically.</p><pre><code># 'states' are from the simulated run above
running_df = pd.DataFrame({'state': states})
running_df.index.name = 'step'
# count how many of each state we used as of each step in the process
state_count_df = pd.get_dummies(data=running_df, 
                                prefix='', 
                                prefix_sep='').cumsum()
# add any missing states because we may not have used them all yet
# and cumsum would drop missing states from the result
model_states = adj_matrix.index.tolist()
missing_columns = set(model_states) - set(state_count_df.columns)
if missing_columns:
    for state in missing_columns:
        state_count_df[state] = 0
        # debugging is easier it we keep the state column-order canonical
        state_count_df = state_count_df[self.model_states]
# add the count aggregates to our running log
running_df = pd.concat([running_df, state_count_df], axis=1)
# turn the current row into its implied distribution
distribution_df = state_count_df.div(state_count_df.sum(axis=1), axis=0)
distributions = [row.to_frame() for _, row in distribution_df.iterrows()]

# ---&gt; changes for distance are below
distances = []
for distribution in distributions:
    # make it look like the structure of the stationary distribution
    distribution.index.name = 'state'
    distribution.rename(columns={0:'probability'}, inplace=True)
    distance = distance_from_stationary(sample=distribution)
    distances.append(distance)
running_df['distance'] = distances
running_df['distribution'] = distributions</code></pre><p>Now we will have that distance and its corresponding distribution available to us for visual comparison to the stationary distribution.</p><p>First, let&#8217;s look at a run with 200 seeds and 10 steps. We&#8217;ll keep the number of steps small this time in order to make it easier to examine details of the image.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!A1M0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!A1M0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png 424w, https://substackcdn.com/image/fetch/$s_!A1M0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png 848w, https://substackcdn.com/image/fetch/$s_!A1M0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png 1272w, https://substackcdn.com/image/fetch/$s_!A1M0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!A1M0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png" width="811" height="604" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:604,&quot;width&quot;:811,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:48409,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!A1M0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png 424w, https://substackcdn.com/image/fetch/$s_!A1M0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png 848w, https://substackcdn.com/image/fetch/$s_!A1M0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png 1272w, https://substackcdn.com/image/fetch/$s_!A1M0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7aa58581-90cb-47d1-8a5f-80f74bdbddea_811x604.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Notice how at at the start (step 0), where all we have is the initial state, the two initial states that are closest to the stationary distribution are <code>'hotel'</code> then <code>'airport'.</code> This makes sense, as these are also the two states with the highest probabilities in the stationary distribution; <code>'hotel'</code> is the highest probability and by distance measure it is the closest. At least JSD as the distance measure isn&#8217;t showing something entirely off-base right from the start.</p><p>As the run progresses, the median outcomes for each initial state converge, and by step 7 we&#8217;re seeing they don&#8217;t differ much from each other. The rest of the run going forward won&#8217;t likely be about specific initial states, but just the overall challenge of any state to result in data that closely approximates the stationary distribution.</p><p>Unfortunately we may be jumping the gun a bit at 7. Let&#8217;s look at seed sensitivity. We can plot the interquartile ranges on each step after the initial state:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Pyar!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Pyar!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png 424w, https://substackcdn.com/image/fetch/$s_!Pyar!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png 848w, https://substackcdn.com/image/fetch/$s_!Pyar!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png 1272w, https://substackcdn.com/image/fetch/$s_!Pyar!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Pyar!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png" width="830" height="588" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/17b32448-f511-45b3-b462-f8367266fff3_830x588.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:588,&quot;width&quot;:830,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:49569,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Pyar!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png 424w, https://substackcdn.com/image/fetch/$s_!Pyar!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png 848w, https://substackcdn.com/image/fetch/$s_!Pyar!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png 1272w, https://substackcdn.com/image/fetch/$s_!Pyar!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F17b32448-f511-45b3-b462-f8367266fff3_830x588.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The per-seed results are still jumping around quite a bit. This isn&#8217;t hugely surprising. The number of states is effectively a sample size, and when the sample size is something like 7 then it doesn&#8217;t take much to perturb the range of distances produced.</p><p>We can get an ever better look at this with initial states in separate plots.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Hw7V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Hw7V!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png 424w, https://substackcdn.com/image/fetch/$s_!Hw7V!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png 848w, https://substackcdn.com/image/fetch/$s_!Hw7V!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png 1272w, https://substackcdn.com/image/fetch/$s_!Hw7V!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Hw7V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png" width="1207" height="794" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:794,&quot;width&quot;:1207,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:55224,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Hw7V!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png 424w, https://substackcdn.com/image/fetch/$s_!Hw7V!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png 848w, https://substackcdn.com/image/fetch/$s_!Hw7V!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png 1272w, https://substackcdn.com/image/fetch/$s_!Hw7V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F983cefa2-26e2-4330-96ea-036332ad0ee8_1207x794.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So not only are the results variable per seed, the variability varies by <code>initial_state</code>. Clearly 7 steps is too soon to declare we&#8217;ve moved beyond bias.</p><p>We had our analytic estimate of 19 steps. Let&#8217;s take the analysis out to 30 steps so we can see 19 in a broader context.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G_Rm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G_Rm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png 424w, https://substackcdn.com/image/fetch/$s_!G_Rm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png 848w, https://substackcdn.com/image/fetch/$s_!G_Rm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png 1272w, https://substackcdn.com/image/fetch/$s_!G_Rm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G_Rm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png" width="1192" height="781" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:781,&quot;width&quot;:1192,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59501,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!G_Rm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png 424w, https://substackcdn.com/image/fetch/$s_!G_Rm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png 848w, https://substackcdn.com/image/fetch/$s_!G_Rm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png 1272w, https://substackcdn.com/image/fetch/$s_!G_Rm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a7ce77f-8ec2-4364-93f0-8f8f697fae6d_1192x781.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The region around 19 steps looks quite stable. The variability is low, and low across all <code>initial_state </code>values. The rate of convergence is slowing down a lot, but that&#8217;s probably not a surprise. As a reminder from the previous article, the spectral gap being closer to 0 than 1 suggested convergence could be sluggish:</p><pre><code>model.spectral_gap</code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5o5w!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5o5w!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png 424w, https://substackcdn.com/image/fetch/$s_!5o5w!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png 848w, https://substackcdn.com/image/fetch/$s_!5o5w!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png 1272w, https://substackcdn.com/image/fetch/$s_!5o5w!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5o5w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png" width="189" height="37" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:37,&quot;width&quot;:189,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1894,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5o5w!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png 424w, https://substackcdn.com/image/fetch/$s_!5o5w!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png 848w, https://substackcdn.com/image/fetch/$s_!5o5w!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png 1272w, https://substackcdn.com/image/fetch/$s_!5o5w!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a63d819-3e28-4770-8631-54abf24d89a6_189x37.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Time to take a look at the actual histograms at step 19 and see what they tell us.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ir9n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ir9n!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png 424w, https://substackcdn.com/image/fetch/$s_!ir9n!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png 848w, https://substackcdn.com/image/fetch/$s_!ir9n!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png 1272w, https://substackcdn.com/image/fetch/$s_!ir9n!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ir9n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png" width="1213" height="791" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:791,&quot;width&quot;:1213,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56537,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ir9n!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png 424w, https://substackcdn.com/image/fetch/$s_!ir9n!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png 848w, https://substackcdn.com/image/fetch/$s_!ir9n!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png 1272w, https://substackcdn.com/image/fetch/$s_!ir9n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f3f3c4d-904a-48df-985b-1e36ad0e1ecc_1213x791.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Not too bad. The estimated convergence seems to have relevance. Since this batch of data went out to 30 steps, we can compare to that. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6wLY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6wLY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png 424w, https://substackcdn.com/image/fetch/$s_!6wLY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png 848w, https://substackcdn.com/image/fetch/$s_!6wLY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png 1272w, https://substackcdn.com/image/fetch/$s_!6wLY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6wLY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png" width="1211" height="789" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/669767d6-86da-4305-a089-6dda259ffc13_1211x789.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:789,&quot;width&quot;:1211,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:57125,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/172060629?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6wLY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png 424w, https://substackcdn.com/image/fetch/$s_!6wLY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png 848w, https://substackcdn.com/image/fetch/$s_!6wLY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png 1272w, https://substackcdn.com/image/fetch/$s_!6wLY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F669767d6-86da-4305-a089-6dda259ffc13_1211x789.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Slightly better, but not game-changing. The important thing is that we aren&#8217;t seeing any meaningful bias based on what the initial state was, and as earlier plots showed the IQR variance based on the seed choice was narrowing as well.</p><h2>Summary</h2><ul><li><p>We wanted to generate data with this model and have a reasonable expectation that the data looked similar to what it was trained on. That is workable at 19 steps and would get progressively better over time.</p></li><li><p>There would be no real need to worry about a better or worse choice of initial state or using data generated across multiple seeds, neither initial-state bias nor seed variance are problematic for chains at least 19 steps long.</p></li><li><p>Our use of the Jensen-Shannon Distance appears safe. This isn&#8217;t really enough material to critique it versus other distance measures, but that wasn&#8217;t the goal. What we wanted was reasonably-similar histogram structure, which is what we have.</p></li><li><p>The estimate of a mixing time (number of steps until viable convergence) lined up with the empirical results, which actually was a nice surprise. Going into the analysis I was skeptical that we would see decent histogram shape that early in the sequence. </p></li></ul><h2>References</h2><ul><li><p><a href="https://gitlab.com/The-Experimentalist/article_support_2025/-/blob/main/article/2025-08-28-markov-chain-convergence-in-python/convergence.ipynb">Jupyter notebook for this article</a>.</p></li><li><p><a href="https://networkx.org/documentation/stable/reference/index.html">NetworkX API reference</a>.</p></li><li><p><a href="https://pandas.pydata.org/docs/reference/">Pandas API reference</a>.</p></li><li><p><a href="https://pypi.org/project/PyDTMC/">PyDTMC PyPi</a>.</p></li><li><p>H&#228;ggstr&#246;m, Olle, &#8220;Stationary distributions,&#8221; in <em><a href="https://www.cambridge.org/core/books/finite-markov-chains-and-algorithmic-applications/EE10AF27811B43B02E05905DC6413467">Finite Markov Chains and Algorithmic Applications</a></em><a href="https://www.cambridge.org/core/books/finite-markov-chains-and-algorithmic-applications/EE10AF27811B43B02E05905DC6413467">. London Mathematical Society Student Texts, vol. 52</a>, Cambridge University Press, 2002, pp. 28-38.</p></li><li><p>Br&#233;maud, Pierre, &#8220;Convergence Rates,&#8221; in <em><a href="https://link.springer.com/book/10.1007/978-3-030-45982-6">Markov Chains: Gibbs Fields, Monte Carlo Simulation and Queues</a></em><a href="https://link.springer.com/book/10.1007/978-3-030-45982-6">, Texts in Applied Mathematics, vol. 31</a>, Springer Cham, 2020, pp. 289&#8211;329.</p></li><li><p><a href="https://en.wikipedia.org/w/index.php?title=Total_variation_distance_of_probability_measures&amp;oldid=123456789">&#8220;Total variation distance of probability measures,&#8221; in </a><em><a href="https://en.wikipedia.org/w/index.php?title=Total_variation_distance_of_probability_measures&amp;oldid=123456789">Wikipedia</a></em>, accessed 2025-08-28, permanent link (revision) &lt;https://en.wikipedia.org/w/index.php?title=Total_variation_distance_of_probability_measures&amp;oldid=123456789&gt;.</p></li><li><p><a href="https://en.wikipedia.org/w/index.php?title=Markov_chain_mixing_time&amp;oldid=1233574639">&#8220;Markov chain mixing time,&#8221; in </a><em><a href="https://en.wikipedia.org/w/index.php?title=Markov_chain_mixing_time&amp;oldid=1233574639">Wikipedia</a></em>, accessed 2025-08-28, permanent link (revision) &lt;https://en.wikipedia.org/w/index.php?title=Markov_chain_mixing_time&amp;oldid=1233574639&gt;.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.the-experimentalist.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Experimentalist is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><a href="https://substack.the-experimentalist.com/p/markov-chain-convergence-in-python">The Experimentalist : Markov Chain Convergence in Python</a> &#169; 2025 by <a href="https://www.linkedin.com/in/reidmpinchback/">Reid M. Pinchback</a> is licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a></p>]]></content:encoded></item><item><title><![CDATA[Markov Chains with NetworkX and PyDTMC]]></title><description><![CDATA[An easy introduction to Markov Chains in Python]]></description><link>https://substack.the-experimentalist.com/p/markov-chains-with-networkx-and-pydtmc</link><guid isPermaLink="false">https://substack.the-experimentalist.com/p/markov-chains-with-networkx-and-pydtmc</guid><dc:creator><![CDATA[Reid M. Pinchback]]></dc:creator><pubDate>Mon, 25 Aug 2025 09:48:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f7cf2652-25d9-4e05-abc1-8d67830bcdfc_540x459.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fGRR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fGRR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif 424w, https://substackcdn.com/image/fetch/$s_!fGRR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif 848w, https://substackcdn.com/image/fetch/$s_!fGRR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif 1272w, https://substackcdn.com/image/fetch/$s_!fGRR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fGRR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif" width="470" height="415" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:415,&quot;width&quot;:470,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2205815,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/gif&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171681483?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fGRR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif 424w, https://substackcdn.com/image/fetch/$s_!fGRR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif 848w, https://substackcdn.com/image/fetch/$s_!fGRR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif 1272w, https://substackcdn.com/image/fetch/$s_!fGRR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ac7d0a-c487-4311-9377-aa8cb98f2382_470x415.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The brief segue into <a href="https://substack.the-experimentalist.com/p/finite-graphs-and-networkx">finite graphs in the previous article</a> has paved the way for developing a little intuition on Markov Chains and working with them in Python. We will want this material for future data engineering explorations.</p><p>The concept of a Markov Chain is easy enough:</p><ul><li><p>You have a collection of states.</p></li><li><p>There are probabilities of transitioning from the current state to various future states.</p></li><li><p>There is no memory behind those probabilities; if you know the current state then you know the probabilities of transitioning next to other states.</p></li></ul><div><hr></div><p><strong>Prev: <a href="https://substack.the-experimentalist.com/p/finite-graphs-and-networkx">Finite Graphs and NetworkX</a> | Next: <a href="https://substack.the-experimentalist.com/p/markov-chain-convergence-in-python">Markov Chain Convergence in Python</a></strong></p><div><hr></div><p>We&#8217;re going to simplify the variety of Markov Chains we&#8217;ll be working with.</p><ul><li><p>We will only discuss chains with a finite number of states.</p></li><li><p>Transitions from one state to the next happen in discrete time steps, not over the flow of continuous time. This lets us organize discussions in terms of time steps t0, t1, &#8230;</p></li></ul><p>With those clarifications, what we&#8217;ll have are Discrete-Time Finite Markov Chains.</p><p>The use of the word &#8216;chain&#8217; is intended to communicate that we can keep repeating this process of random state transitions over and over again. That assumes there are no states without any outgoing transitions, which are called <strong>absorbing states</strong>, which can terminate some randomly-generated walks.</p><h2>The Graph Connection</h2><p>Remembering <a href="https://substack.the-experimentalist.com/p/finite-graphs-and-networkx">our previous discussion on graphs</a>, a Directed Finite Graph has:</p><ul><li><p><strong>Nodes</strong>.</p></li><li><p><strong>Edges</strong> between pairs of nodes, and those edges are ordered from <strong>source</strong> to <strong>target</strong>.</p></li><li><p>When an edge leaves a source node, that is an <strong>outedge</strong>.</p></li><li><p>When an edge arrives (the arrow-head in a picture points at) a target node, that is an <strong>inedge</strong>.</p></li><li><p>It is something useful to assign attributes like a numeric <strong>weight</strong> to edges.</p></li></ul><p>We&#8217;ll add to this a little bit of simplified statistical terminology.</p><ul><li><p>A <strong>probability</strong> is a real number p where 0 &#8804; p &#8804; 1.</p></li><li><p>A <strong>discrete probability distribution</strong> is a finite collection of probability values that add up to 1.</p></li><li><p>If we have estimated or assigned probabilities to a collection of possible events, and those probabilities form a probability distribution, we refer to that collection of events as being <strong>stochastic</strong>.</p></li></ul><p>And now we&#8217;re ready to characterize our Discrete-Time Finite Markov Chains (DTFMCs to save typing) in terms of Directed Finite Graphs (DFGs).</p><ul><li><p>States in a DTFMC are nodes in a DFG.</p></li><li><p>If it is possible to transition between two states <strong>a</strong> and <strong>b</strong> in a DTFMC, then that is represented as the edge <strong>(a, b)</strong> in a DFG.</p></li><li><p>The probability of such a transition from state <strong>a</strong> to state <strong>b</strong> in a DTFMC is represented as the weight of the edge <strong>(a, b)</strong>.</p></li><li><p>In an adjacency matrix for the DFG, each row corresponds to a source state and each column corresponds to a target state. All non-zero values will be the weights (probabilities of a possible state-to-state transition).</p></li><li><p>For every node (state) in the DFG, its collection of weighted outedges (representing state transitions with non-zero probability) is stochastic.</p></li></ul><p>That last two points tell us that when the DFG for a DTFMC is represented as an <strong>adjacency matrix</strong>, each row will either sum to:</p><ul><li><p>0 if and only if that state has no outedges.</p></li><li><p>1 if and only if that state has at least 1 outedge.</p></li></ul><p>This representation of a Markov Chain is sometimes referred to as <strong>row-stochastic</strong> or <strong>row-normalized</strong>. It would be possible to construct a matrix that instead was <strong>column-stochastic</strong> or <strong>column-normalized</strong>; in that case we would be seeing the probability distribution for how possible source states could transition to a target state. Row-stochastic is for understanding how a specific source leads to many potential targets, Column-stochastic is for understanding how many potential sources lead to a specific target. In this article we&#8217;ll be working with row-stochastic representations.</p><h2>NetworkX Example</h2><p>Consider the following taxi trip model as a Markov Chain:</p><ul><li><p>States are destinations.</p></li><li><p>Edges are trips between destinations.</p></li><li><p>Weight probabilities are the odds of going from one destination to another.</p></li></ul><p>Our specific data will be</p><ul><li><p>From the airport, 70% of trips go to the hotel, 20% the hospital, 10% the mall.</p></li><li><p>From the hospital, 60% of trips go to the hotel, 40% the airport.</p></li><li><p>From the hotel, 40% of trips go to the mall, 40% the airport, 20% the hospital.</p></li><li><p>From the mall, 70% of trips go to the hotel, 20% the airport, 10% the hospital.</p></li></ul><p>As shown in the previous article, we can represent such a weighted DFG as an adjacency matrix:</p><pre><code>adj_rows = dict(
    airport= [  0, 0.2, 0.7, 0.1],
    hospital=[0.4,   0, 0.6,   0],
    hotel=   [0.4, 0.2,   0, 0.4],
    mall=    [0.2, 0.1, 0.7,   0],
)
adj_matrix = pd.DataFrame.from_dict(adj_rows,
                                    orient='index',
                                    dtype=float,
                                    columns=list(adj_rows.keys()))
graph = nx.from_pandas_adjacency(adj_matrix, create_using=nx.DiGraph)</code></pre><p>We can use NetworkX to describe the appearance of the graph. First we describe the nodes and node labels:</p><pre><code>node_colors = dict(
    airport='purple',
    hospital='red',
    hotel='blue',
    mall='darkgreen',
)

for node_name, node_color in node_colors.items():
    nx.draw_networkx_nodes(graph,
                           pos=node_positions,
                           nodelist=[node_name],
                           node_color=node_color,
                           **node_params())
label_colors = {node_name: node_name for node_name in node_colors}
nx.draw_networkx_labels(graph,
                        pos=node_positions,
                        labels=label_colors,
                        **node_label_params())</code></pre><p>Then we describe the edges and edge labels:</p><pre><code>for node in graph:
    out_edges_view = typing.cast(nx.reportviews.OutEdgeView, graph.out_edges)
    nx.draw_networkx_edges(graph,
                           pos=node_positions,
                           edgelist=out_edges_view(node),
                           edge_color=node_colors[node],
                           **edge_params())
    for src, target, data in out_edges_view(node, data=True):
        edge_label = {(src, target): data['weight']}
        nx.draw_networkx_edge_labels(graph,
                                     pos=node_positions,
                                     edge_labels=edge_label,
                                     font_color=node_colors[node],
                                     **edge_label_params())</code></pre><p>This renders as:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lvOg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lvOg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png 424w, https://substackcdn.com/image/fetch/$s_!lvOg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png 848w, https://substackcdn.com/image/fetch/$s_!lvOg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png 1272w, https://substackcdn.com/image/fetch/$s_!lvOg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lvOg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png" width="794" height="587" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/baa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:587,&quot;width&quot;:794,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:61941,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171681483?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lvOg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png 424w, https://substackcdn.com/image/fetch/$s_!lvOg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png 848w, https://substackcdn.com/image/fetch/$s_!lvOg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png 1272w, https://substackcdn.com/image/fetch/$s_!lvOg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbaa1b9c2-949d-41da-84f8-bae3a1a46d1b_794x587.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The outedges of each node are colored to match their source. The weights of all edges of the same color will add up to 1, reflecting their stochastic nature.</p><p>Now we have a visualization of a Markov Chain. From any state (node), in one discrete time step travel will happen on one of the correspondingly-colored outedges to a target node, with probability of that event as shown by the weight on the edge.</p><h2>Counting DFG Walks</h2><p>Going back for a moment to our graphs, we will introduce an additional concept.</p><ul><li><p>A <strong>walk</strong> is a sequence of connected edges in a graph.</p></li><li><p>If the graph is a directed graph, edges only connect if each intermediate node functions first as a target for an edge, and then as a source for the next edge. Connections must respect directionality. If a graph has a directed edge <strong>(a, b)</strong>, and another edge <strong>(c, b)</strong>, you cannot directly connect those edges in a walk because they are going in opposite directions.</p></li></ul><p>Given a particular DFG, between any two nodes, how many walks can we identify of length 2? Length 3? The answer is given by raising the unweighted adjacency matrix to the corresponding power. In other words if <strong>A</strong> is the adjacency matrix of a DFG, then the matrix product <strong>A * A</strong> computes the number of paths of length 2, and so on.</p><p>This can be illustrated with NetworkX and an edge list for weather transitions:</p><pre><code>edge_rows = [
    dict(src='cloud', dest='cloud'),
    dict(src='cloud', dest='hail'),
    dict(src='cloud', dest='rain'),
    dict(src='cloud', dest='snow'),
    dict(src='cloud', dest='sun'),
    dict(src='hail', dest='cloud'),
    dict(src='hail', dest='hail'),
    dict(src='hail', dest='rain'),
    dict(src='rain', dest='cloud'),
    dict(src='rain', dest='hail'),
    dict(src='rain', dest='rain'),
    dict(src='rain', dest='snow'),
    dict(src='snow', dest='cloud'),
    dict(src='snow', dest='rain'),
    dict(src='snow', dest='snow'),
    dict(src='sun', dest='cloud'),
    dict(src='sun', dest='sun'),
]
edge_list_df = pd.DataFrame(edge_rows)
graph = nx.from_pandas_edgelist(edge_list_df,
                                source='src',
                                target='dest',
                                create_using=nx.DiGraph)
adj_matrix = nx.to_pandas_adjacency(graph, dtype=int)
adj_matrix</code></pre><p>The last step converted the edge list to an adjacency matrix as a Pandas DataFrame:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AhSg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AhSg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png 424w, https://substackcdn.com/image/fetch/$s_!AhSg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png 848w, https://substackcdn.com/image/fetch/$s_!AhSg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png 1272w, https://substackcdn.com/image/fetch/$s_!AhSg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AhSg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png" width="666" height="243" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:243,&quot;width&quot;:666,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:11748,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171681483?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AhSg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png 424w, https://substackcdn.com/image/fetch/$s_!AhSg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png 848w, https://substackcdn.com/image/fetch/$s_!AhSg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png 1272w, https://substackcdn.com/image/fetch/$s_!AhSg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ee7710-22ab-4de4-b419-dbc24598ac4c_666x243.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This data restricts sun to not spontaneously transitioning to hail or snow or rain. We would need a walk of length at least 2 to have a chance to transition through cloudy weather first. We can accomplish that with matrix multiplication as a &#8220;dot&#8221; product:</p><pre><code>adj_matrix @ adj_matrix</code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GLFc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GLFc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png 424w, https://substackcdn.com/image/fetch/$s_!GLFc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png 848w, https://substackcdn.com/image/fetch/$s_!GLFc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png 1272w, https://substackcdn.com/image/fetch/$s_!GLFc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GLFc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png" width="666" height="239" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:239,&quot;width&quot;:666,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:12901,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171681483?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GLFc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png 424w, https://substackcdn.com/image/fetch/$s_!GLFc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png 848w, https://substackcdn.com/image/fetch/$s_!GLFc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png 1272w, https://substackcdn.com/image/fetch/$s_!GLFc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a64c554-88a7-4bf7-a3f7-4278e932e5f7_666x239.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>We can see, correctly,  that there is only 1 walk of length 2 that starts with sun and ends with snow. </p><p>Also correct is the number of ways to start with sun and end with cloudy weather. There are 2: sun &#8594; sun &#8594; cloud and sun &#8594; cloud &#8594; cloud.</p><p>We don&#8217;t need to work with the adjacency matrix and figure out how many dot products to type in for higher powers. NetworkX provides a function to specify the desired power directly. It returns a dict of dicts, which we can make prettier as a Pandas DataFrame:</p><pre><code>pd.DataFrame(nx.number_of_walks(graph, 2))</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zQQ_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zQQ_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png 424w, https://substackcdn.com/image/fetch/$s_!zQQ_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png 848w, https://substackcdn.com/image/fetch/$s_!zQQ_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png 1272w, https://substackcdn.com/image/fetch/$s_!zQQ_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zQQ_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png" width="667" height="241" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:241,&quot;width&quot;:667,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:12911,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171681483?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zQQ_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png 424w, https://substackcdn.com/image/fetch/$s_!zQQ_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png 848w, https://substackcdn.com/image/fetch/$s_!zQQ_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png 1272w, https://substackcdn.com/image/fetch/$s_!zQQ_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16c02b4c-78d4-4670-880c-bf0719ca578e_667x241.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The results are the same as the calculation on the adjacency matrix.</p><p>The rest of the discussion will now return to exploring the taxi data.</p><h2>Measuring DTFMC Walk Probabilities</h2><p>In the DFG walk computations just shown, the weights in the adjacency matrix were all 1 to denote the directed edge. However, this same computation also works when the weights are other numbers, like probabilities. What is computed is the probability-weighted sum of those walks. This means:</p><ul><li><p>Compute the <strong>n</strong>&#8217;th power of a Markov Chain.</p></li><li><p>We will get the probability of starting in a particular state <strong>a</strong>, and ending up in another state <strong>b</strong>. If there is no way to get from <strong>a</strong> to <strong>b</strong> then the value is <strong>0</strong>.</p></li><li><p>This is the probability to have that outcome after exactly <strong>n</strong> steps in the walk.</p></li><li><p>The probability does not count the possibility of getting from <strong>a</strong> to <strong>b</strong> in fewer or greater than <strong>n</strong> steps.</p></li></ul><p>Let&#8217;s try this for the taxi DTFMC defined earlier. The Pandas DataFrame for that adjacency matrix is:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kRlY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kRlY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png 424w, https://substackcdn.com/image/fetch/$s_!kRlY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png 848w, https://substackcdn.com/image/fetch/$s_!kRlY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png 1272w, https://substackcdn.com/image/fetch/$s_!kRlY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kRlY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png" width="670" height="208" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:208,&quot;width&quot;:670,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:14927,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171681483?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kRlY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png 424w, https://substackcdn.com/image/fetch/$s_!kRlY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png 848w, https://substackcdn.com/image/fetch/$s_!kRlY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png 1272w, https://substackcdn.com/image/fetch/$s_!kRlY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F493cec15-d61c-45a7-aed2-41581c3a538d_670x208.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>We can compute the 3rd power of that graph. We&#8217;ll need to use the Pandas dot product for this, as NetworkX doesn&#8217;t understand weighting.</p><pre><code>(adj_matrix @ adj_matrix) @ adj_matrix</code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dkZj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dkZj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png 424w, https://substackcdn.com/image/fetch/$s_!dkZj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png 848w, https://substackcdn.com/image/fetch/$s_!dkZj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png 1272w, https://substackcdn.com/image/fetch/$s_!dkZj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dkZj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png" width="672" height="209" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f25ff331-a22d-49f7-8308-63e925a553a5_672x209.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:209,&quot;width&quot;:672,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:15236,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171681483?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dkZj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png 424w, https://substackcdn.com/image/fetch/$s_!dkZj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png 848w, https://substackcdn.com/image/fetch/$s_!dkZj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png 1272w, https://substackcdn.com/image/fetch/$s_!dkZj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff25ff331-a22d-49f7-8308-63e925a553a5_672x209.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>As we can see, starting at the mall and returning again on the third trip has an 11.2% probability. Also note that no entries are zero. We&#8217;ll discuss the implications shortly.</p><h2>Markov Chains and PyDTMC</h2><p>So far we&#8217;ve just been exploring how to represent a Markov Chain as a graph with an associated matrix with per-state probability distributions. We can do more to analyze the characteristics of the model of taxi data that we&#8217;ve built.</p><pre><code>import pydtmc as mc

model = mc.MarkovChain(adj_matrix)
print(model)</code></pre><p>From this we can get a summary report about our model:</p><pre><code>DISCRETE-TIME MARKOV CHAIN
 SIZE:           4
 RANK:           4
 CLASSES:        1
  &gt; RECURRENT:   1
  &gt; TRANSIENT:   0
 ERGODIC:        YES
  &gt; APERIODIC:   YES
  &gt; IRREDUCIBLE: YES
 ABSORBING:      NO
 MONOTONE:       NO
 REGULAR:        YES
 REVERSIBLE:     NO
 SYMMETRIC:      NO</code></pre><p>The important characteristics for now are that the model is:</p><ul><li><p><strong>Regular</strong>. This is a particularly strong property for a Markov Model to possess, and makes other operations much more approachable. It means that there is a probability distribution of states that all future state transitions will converge to. </p></li><li><p><strong>Ergodic</strong>. All the states are reachable from each other after a finite number of steps (<strong>irreducible</strong>), and the long-term distribution of states does not oscillate with periodicity (<strong>aperiodic</strong>). Above there was a place where it was pointed out that the adjacency matrix raised to the third power had all positive entries: that showed the matrix was recurrent.</p></li><li><p><strong>Recurrent</strong>: With a single recurrent class (meaning all states are in that class), we have that no matter which state we start in, we will return to it infinitely many times as we traverse more and more time steps.</p></li></ul><p>Regular implies Ergodic, but the reverse is not always true.</p><p>Because we are dealing with Finite Markov Models, Recurrent implies Irreducible, and Irreducible implies Recurrent. These relationships would not hold for infinite models.</p><p>We can use PyDTMC to get that stationary distribution.</p><pre><code>model.stationary_distributions</code></pre><p>This is the probability distribution for visiting the states over time. The order of the states here is the same as the order of the rows in the original adjacency matrix, thus we spend the most time at the hotel: <code>0.4028777</code>.</p><pre><code>[array([0.25899281, 0.15107914, 0.4028777 , 0.18705036])]</code></pre><p>We can obtain some insight into whether the model finds it easy or difficult to converge to that distribution. One way is by plotting the eigenvalues of the matrix:</p><pre><code>mc.plot_eigenvalues(model)</code></pre><p>From that we get:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sTYs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sTYs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png 424w, https://substackcdn.com/image/fetch/$s_!sTYs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png 848w, https://substackcdn.com/image/fetch/$s_!sTYs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png 1272w, https://substackcdn.com/image/fetch/$s_!sTYs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sTYs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png" width="576" height="598" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:598,&quot;width&quot;:576,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:41124,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171681483?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sTYs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png 424w, https://substackcdn.com/image/fetch/$s_!sTYs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png 848w, https://substackcdn.com/image/fetch/$s_!sTYs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png 1272w, https://substackcdn.com/image/fetch/$s_!sTYs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ff1d221-7f5d-4e53-960a-4144b6c24adf_576x598.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Take notice of the two largest-magnitude eigenvalues. The wide red ring shows the distance between them, called the <strong>spectral gap</strong>. The closer they are, the more difficult it is for the future probabilities to generate walk that converge to the stationary distribution.</p><p>We can find out the spectral gap directly:</p><pre><code>model.spectral_gap</code></pre><ul><li><p>When the value is closer to 1, convergence is faster.</p></li><li><p>When it is closer to 0, convergence is slower.</p></li></ul><pre><code>0.31078141211367716</code></pre><p>In our case convergence is a little sluggish.</p><p>We can get further insight into convergence with <strong>Spectral Density</strong>.</p><pre><code>model.density</code></pre><p>This tells us how variable the data is:</p><pre><code>0.9166666666666666</code></pre><ul><li><p>A value near 1 suggests convergence is easier.</p></li><li><p>Near 0 indicates convergence is harder, and it would still mean that no matter what the spectral gap may be suggesting.</p></li></ul><p>While it may sound a little counter-intuitive, data that is more variable actually makes convergence easier. When it is less variable then you have some low-probability transitions in the matrix that can keep the model stuck instead of moving freely around all its states.</p><p>In the next article we&#8217;ll explore what that convergence behavior looks like.</p><h2>References</h2><ul><li><p><a href="https://gitlab.com/The-Experimentalist/article_support_2025/-/blob/main/article/2025-08-25-markov-chains-with-networkx-and-pydtmc/markov-chain.ipynb">Jupyter notebook for this article</a>.</p></li><li><p><a href="https://networkx.org/documentation/stable/reference/index.html">NetworkX API reference</a>.</p></li><li><p><a href="https://pandas.pydata.org/docs/reference/">Pandas API reference</a>.</p></li><li><p><a href="https://pypi.org/project/PyDTMC/">PyDTMC PyPi</a>.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.the-experimentalist.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Experimentalist is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><a href="https://substack.the-experimentalist.com/p/markov-chains-with-networkx-and-pydtmc">The Experimentalist : Markov Chains with NetworkX and PyDTMC</a> &#169; 2025 by <a href="https://www.linkedin.com/in/reidmpinchback/">Reid M. Pinchback</a> is licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Finite Graphs and NetworkX]]></title><description><![CDATA[Add representation and analysis of graph networks to your Python toolkit]]></description><link>https://substack.the-experimentalist.com/p/finite-graphs-and-networkx</link><guid isPermaLink="false">https://substack.the-experimentalist.com/p/finite-graphs-and-networkx</guid><dc:creator><![CDATA[Reid M. Pinchback]]></dc:creator><pubDate>Fri, 22 Aug 2025 01:00:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!h81y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!h81y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!h81y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg 424w, https://substackcdn.com/image/fetch/$s_!h81y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg 848w, https://substackcdn.com/image/fetch/$s_!h81y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!h81y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!h81y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg" width="514" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:514,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:284227,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171538815?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!h81y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg 424w, https://substackcdn.com/image/fetch/$s_!h81y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg 848w, https://substackcdn.com/image/fetch/$s_!h81y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!h81y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9b34f5a-b0b8-40ee-9a82-bb6aafe49b39_514x400.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Many areas of practical data analysis and systems reasoning are grounded in the idea of using a finite graph to represent a problem. Not only does this provide a way to structure data, but the structure itself may suit analysis by a variety of techniques.</p><p>We&#8217;re going to keep the context very straightforward in order to build intuition over a few articles. The dusty old math texts can stay on their shelves for this one.</p><div><hr></div><p><strong>Next: <a href="https://substack.the-experimentalist.com/p/markov-chains-with-networkx-and-pydtmc">Markov Chains with NetworkX and PyDTMC</a></strong></p><div><hr></div><h2>Finite Graph Terminology</h2><p><strong>Finite Graph</strong>: a finite collection of nodes and edges.</p><p><strong>Node</strong>: an object in a graph that will have 0 or more edges connecting it to other nodes.</p><p><strong>Edge</strong>: a line in the graph that connects two nodes. It is acceptable for the same node to be on both ends of a connection.</p><pre><code>import networkx as nx
import matplotlib.pyplot as plt
import pandas as pd
import random
import typing

graph: nx.Graph = nx.Graph()
graph.add_nodes_from(['a', 'b', 'c', 'd', 'e'])
graph.add_edges_from([('a', 'b'), ('a', 'c'), ('b', 'd'),
                      ('c', 'c'), ('c', 'e'), ('e','b')])
node_positions: NodeLayout  = nx.circular_layout(graph)
nx.draw(graph, pos=node_positions, **draw_params())</code></pre><p>Tuples like <code>('a', 'b')</code> are the nodes at either end of that edge. As this is an undirected graph, <code>('b', 'a')</code> would achieve the same.</p><p>The <code>draw_params()</code> function is a convenience for boilerplate reduction on setting rendering parameters across multiple examples.</p><pre><code>def override_params(params: dict[str, typing.Any],
                    **kwargs: typing.Any) -&gt; None:
    for key, value in kwargs.items():
        params[key] = value

def draw_params(**kwargs: typing.Any) -&gt; dict[str, typing.Any]:
    params: dict[str, typing.Any] = dict(
        arrows=True,
        arrowsize=17,
        edge_color='darkgray',  # colors graph edges
        edgecolors='darkgray',  # colors border pixels of nodes
        font_color='black',
        font_size=15,
        font_weight='semibold',
        node_color='lightgray',
        node_size=525,
        width=2.5,
        with_labels=True,
    )
    override_params(params, **kwargs)
    return params</code></pre><p>The complete code (<a href="https://gitlab.com/The-Experimentalist/article_support_2025/-/blob/main/article/2025-08-21-finite-graphs-and-networkx/finite-graphs.ipynb">available in a Jupyter notebook</a>) renders:</p><p> </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CBdR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CBdR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!CBdR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!CBdR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!CBdR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CBdR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png" width="794" height="505" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3884c568-add4-445c-8bb2-0eca94978447_794x505.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:505,&quot;width&quot;:794,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:29232,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171538815?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CBdR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!CBdR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!CBdR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!CBdR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3884c568-add4-445c-8bb2-0eca94978447_794x505.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Display of node labels is optional, which is a useful feature for large graphs. Just add <code>with_labels=False</code> to <code>nx.draw()</code> via the helper method <code>draw_params()</code>:</p><pre><code>nx.draw(graph, pos=node_positions, **draw_params(with_labels=False))</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!unPg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!unPg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!unPg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!unPg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!unPg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!unPg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png" width="794" height="505" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:505,&quot;width&quot;:794,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:29299,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171538815?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!unPg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!unPg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!unPg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!unPg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3013f9f-0ffe-42d1-a712-ea11b3e9efbe_794x505.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>We can extend this idea a little further. We can give those edges a sense of direction.</p><h2>Directed Finite Graph Terminology</h2><p><strong>Node</strong>: as defined previously.</p><p><strong>Edge</strong>: a line in a graph that has a source node and a target node. When drawn, an arrow head points to the target node.</p><p>If the edges aren&#8217;t directed we call it an <strong>Undirected Finite Graph</strong> (or <strong>Undirected Graph</strong> for short), otherwise a <strong>Directed Finite Graph</strong> (again, <strong>Directed Graph</strong> for short).</p><p>Nodes in a directed graph now gain a bit more structure:</p><ul><li><p><strong>Outgoing Edges</strong>: the directed edges connected to a node that have that node as a source. Also called &#8220;<strong>outedges</strong>&#8221; in some math texts.</p></li><li><p><strong>Incoming Edges</strong>: the directed edges connected to a node that have that node as a target. Also called &#8220;<strong>inedges</strong>&#8221; in some math texts.</p></li></ul><p>Representing edges is no different, except now the order of the identifier pairs matters when it didn&#8217;t matter for undirected edges. The first member of a pair is the source, the second is the target. The only substantial difference is the change in the NetworkX class used when defining the graph. We change the class from <code>nx.Graph</code> to <code>nx.DiGraph</code>:</p><pre><code>graph: nx.Graph = nx.DiGraph()
graph.add_nodes_from(['a', 'b', 'c', 'd', 'e'])
graph.add_edges_from([('a', 'b'), ('a', 'c'), ('b', 'd'),
                      ('c', 'c'), ('c', 'e'), ('e','b')])
node_positions: NodeLayout  = nx.circular_layout(graph)
nx.draw(graph, pos=node_positions, **draw_params())</code></pre><p>With this change the rendering becomes:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HAQ2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HAQ2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!HAQ2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!HAQ2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!HAQ2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HAQ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png" width="794" height="505" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/14c95c95-f775-4358-936f-bf6081554d0c_794x505.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:505,&quot;width&quot;:794,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:29590,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171538815?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HAQ2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!HAQ2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!HAQ2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!HAQ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14c95c95-f775-4358-936f-bf6081554d0c_794x505.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Alternate Representations</h2><p>It can be mathematically useful, particularly for directed graphs, to think of them as corresponding to a matrix. Each row of a matrix corresponds to a source node, and each column corresponds to a target node. This is referred to as an <strong>Adjacency Matrix</strong>.</p><pre><code>adj_rows: dict[typing.Hashable, list[typing.Any]] = dict(
    a=[0, 1, 1, 0, 0],
    b=[0, 0, 0, 1, 0],
    c=[0, 0, 1, 0, 1],
    d=[0, 0, 0, 0, 0],
    e=[0, 1, 0, 0, 0]
)
adj_matrix = pd.DataFrame.from_dict(adj_rows,
                                    orient='index',
                                    dtype=int,
                                    columns=list(adj_rows.keys()))
graph = nx.from_pandas_adjacency(adj_matrix, create_using=nx.DiGraph)
node_positions: NodeLayout  = nx.circular_layout(graph)
nx.draw(graph, pos=node_positions, **draw_params())</code></pre><p>We use the Pandas trick of providing a keyed dictionary for rows and specifying the orientation. Thinking in terms of a known source having various targets is more intuitive than what column-specified data would force on us: to think in terms of one target having multiple sources. It also allows the visual aid of the text for populating <code>adj_rows</code> to correspond to how we have been taught to conceive of matrices. All we need to do is inform Pandas what the column keys are, which in this case is the same as the row keys: <code>columns=list(adj_rows.keys())</code>.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BcFI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BcFI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!BcFI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!BcFI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!BcFI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BcFI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png" width="794" height="505" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:505,&quot;width&quot;:794,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31481,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171538815?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BcFI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!BcFI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!BcFI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!BcFI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1a4c917-a920-4a03-bb3e-4a9c027e858f_794x505.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The output is the same as before, which was the goal. This representation can be convenient when the existing understanding of a graph is organized in terms of outgoing edges, because all the outgoing edge connections for a node form the matrix row for that node.</p><p>The number 1 in the matrix indicates a directed edge from the source (the row index) to the target (the column index). Using 0 and 1 is a traditional starting point for adjacency matrices because it supports some useful linear algebra, but there is no constraint that only the number 1 be used to indicate a connection. Any non-zero value can represent, for example, weight or length.</p><p>The disadvantage of this representation, at least with NetworkX, is that the only information this can capture are node names, edge directions, and a single edge weight. Adjacency matrices for NetworkX do not support the idea of structured objects so we couldn&#8217;t use one to specify multiple per-edge attributes. For that we need <strong>Edge Lists</strong>.</p><pre><code>edge_rows = [
    dict(src='a', dest='b'),
    dict(src='a', dest='c'),
    dict(src='b', dest='d'),
    dict(src='c', dest='c'),
    dict(src='c', dest='e'),
    dict(src='e', dest='b'),
]
edge_list_df: pd.DataFrame = pd.DataFrame(edge_rows)
graph = nx.from_pandas_edgelist(edge_list_df,
                                source='src',
                                target='dest',
                                create_using=nx.DiGraph)
node_positions: NodeLayout  = nx.circular_layout(graph)
nx.draw(graph, pos=node_positions, **draw_params())</code></pre><p> The situation has changed a bit:</p><ul><li><p>Each edge must be specified as a dictionary with consistent keys indicating the source and target of the edge.</p></li><li><p>The edge list is converted into a Pandas <code>DataFrame</code>, and the dictionary keys become the column names.</p></li><li><p>We use <code>nx.from_pandas_edgelist()</code> to convert the <code>DataFrame</code> into a graph, which requires specifying the keys that were used for the source and target.</p></li></ul><p>The final rendering though, at least for this data, is unchanged:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eZQi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eZQi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!eZQi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!eZQi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!eZQi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eZQi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png" width="794" height="505" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:505,&quot;width&quot;:794,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:30527,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171538815?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eZQi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!eZQi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!eZQi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!eZQi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec072bb-7fce-4ea4-ada7-a2d274eea07e_794x505.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now we can push this further to add more attributes to the edge data:</p><pre><code>def random_color_channel() -&gt; float:
    if not hasattr(random_color_channel, 'rand'):
        random_color_channel.rand = random.Random(42)
    return random_color_channel.rand.uniform(0.5, 1)

def random_rgb_color() -&gt; tuple[float, float, float]:
    return random_color_channel(),
           random_color_channel(),
           random_color_channel()

edge_rows = [
    dict(src='a', dest='b', weight=1, color=random_rgb_color()),
    dict(src='a', dest='c', weight=3, color=random_rgb_color()),
    dict(src='b', dest='d', weight=2, color=random_rgb_color()),
    dict(src='c', dest='c', weight=1, color=random_rgb_color()),
    dict(src='c', dest='e', weight=2, color=random_rgb_color()),
    dict(src='e', dest='b', weight=1, color=random_rgb_color()),
]
edge_list_df: pd.DataFrame = pd.DataFrame(edge_rows)
graph = nx.from_pandas_edgelist(edge_list_df,
                                source='src',
                                target='dest',
                                edge_attr=['weight', 'color'],
                                create_using=nx.DiGraph)
edge_weights = nx.get_edge_attributes(graph, 'weight')
edge_colors = nx.get_edge_attributes(graph, 'color').values()
node_positions: NodeLayout  = nx.circular_layout(graph)
nx.draw(graph, pos=node_positions, **draw_params(edge_color=edge_colors))
nx.draw_networkx_edge_labels(graph, pos=node_positions,
                             **edge_label_params(edge_labels=edge_weights))</code></pre><p>Now we&#8217;re adding some complexity:</p><ul><li><p>Edges have two attributes: <code>weight</code> and <code>color</code>.</p></li><li><p>The color is an RGB triple where each channel is a float in the range [0, 1].</p></li><li><p>When using <code>nx.from_pandas_edgelist()</code> to produce a graph we have to tell it about the per-edge attributes: <code>edge_attr=['weight', 'color']</code></p></li><li><p>NetworkX doesn&#8217;t do much with complex edges so we have to extract the data as <code>edge_weights</code> and <code>edge_colors</code> to use later as parameters.</p></li><li><p>The <code>nx.draw()</code> function has limited smarts on some forms of rendering, so we include use of <code>nx.draw_networkx_edge_labels()</code> to render the weights as values on each edge.</p></li><li><p>The way <code>nx.draw()</code> and <code>nx.draw_networkx_edge_labels()</code> share an understanding of node locations is via the <code>node_positions</code> layout object. In the previous examples it was only used once per rendering so its role was less apparent, but here we see it used to layer on rendering details. The object provides node coordinates in the rendering. I added a Python type alias to cover the kinds of data passed back by various layout algorithms:</p></li></ul><pre><code>NodeLayout: typing.TypeAlias = dict[typing.Hashable, list[float]]</code></pre><p>Now we get a much richer rendering with per-edge weight labels and per-edge colors:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kAI-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kAI-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!kAI-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!kAI-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!kAI-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kAI-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png" width="794" height="505" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:505,&quot;width&quot;:794,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38601,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/171538815?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kAI-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png 424w, https://substackcdn.com/image/fetch/$s_!kAI-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png 848w, https://substackcdn.com/image/fetch/$s_!kAI-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png 1272w, https://substackcdn.com/image/fetch/$s_!kAI-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F607954dd-7a88-40de-9e9d-3bddf994337c_794x505.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In future articles I expect to need to use various analytic techniques on graphs. Even without that, NetworkX can help with rendering images that support an explanation for how data is being transformed or analyzed.</p><h2>References</h2><ul><li><p><a href="https://gitlab.com/The-Experimentalist/article_support_2025/-/blob/main/article/2025-08-21-finite-graphs-and-networkx/finite-graphs.ipynb">Jupyter notebook for this article</a>.</p></li><li><p><a href="https://matplotlib.org/stable/api/">Matplotlib API reference</a>.</p></li><li><p><a href="https://networkx.org/documentation/stable/reference/index.html">NetworkX API reference</a>.</p></li><li><p><a href="https://pandas.pydata.org/docs/reference/">Pandas API reference</a>.</p></li></ul><h2>Edits</h2><ul><li><p><strong>2025-08-22:</strong> Added terminology for <strong>inedges</strong> and <strong>outedges</strong>.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.the-experimentalist.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Experimentalist is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><a href="https://substack.the-experimentalist.com/p/finite-graphs-and-networkx">The Experimentalist : Finite Graphs and NetworkX</a> &#169; 2025 by <a href="https://www.linkedin.com/in/reidmpinchback/">Reid M. Pinchback</a> is licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a></p>]]></content:encoded></item><item><title><![CDATA[Client Connectivity with PostgreSQL]]></title><description><![CDATA[Resolving database problems can begin with chasing down clients]]></description><link>https://substack.the-experimentalist.com/p/client-connectivity-with-postgresql</link><guid isPermaLink="false">https://substack.the-experimentalist.com/p/client-connectivity-with-postgresql</guid><dc:creator><![CDATA[Reid M. Pinchback]]></dc:creator><pubDate>Fri, 15 Aug 2025 03:28:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Pv71!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Pv71!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Pv71!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Pv71!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Pv71!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Pv71!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Pv71!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg" width="514" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:514,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:280960,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Pv71!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Pv71!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Pv71!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Pv71!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b539003-51a8-4ad6-8edd-c482866bc6c1_514x400.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There are times when you need to chase down database client connections in PostgreSQL. Examples of the issues that arise are:</p><ul><li><p>Having too many idle connections, which can pointlessly throttle database performance due to the backend process resources allocated to each.</p></li><li><p>Having too many connections in total relative to the semaphore array configuration of the database, resulting in new connections being denied.</p></li><li><p>Holding transaction-scoped locks for too long or gaining them in a problematic order, which can create errors or deadlocks in other operations that need some of those same locks for work to proceed.</p></li><li><p>Holding transactions open too long regardless of any locking issues, which can thwart maintenance operations like vacuums and backups.</p></li><li><p>Burning through transaction ids at an unusual rate, which might trigger id wrap-around too fast for vacuum operations to keep up and thus cause the database to go into recovery mode.</p></li><li><p>Connection activity may be unexpected or suspicious, in which case you may have a security incident to investigate.</p></li></ul><div><hr></div><p><strong>Prev:</strong> <strong><a href="https://substack.the-experimentalist.com/p/postgresql-17-on-vagrant-and-virtualbox">PostgreSQL 17 on Vagrant and VirtualBox</a></strong></p><div><hr></div><h2>The TCP/IP Story</h2><p>When we set up server processes we often find ourselves having to configure one or more ports for access. We become very aware of server-side addressing via IP or DNS name plus a port. What is easy to forget is that the client end of the connection also has addressing information associated with it.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UEB0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UEB0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png 424w, https://substackcdn.com/image/fetch/$s_!UEB0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png 848w, https://substackcdn.com/image/fetch/$s_!UEB0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png 1272w, https://substackcdn.com/image/fetch/$s_!UEB0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UEB0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png" width="591" height="201" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:201,&quot;width&quot;:591,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:35771,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UEB0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png 424w, https://substackcdn.com/image/fetch/$s_!UEB0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png 848w, https://substackcdn.com/image/fetch/$s_!UEB0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png 1272w, https://substackcdn.com/image/fetch/$s_!UEB0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe12f402b-163e-4dc9-8869-f43e545e33c3_591x201.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p>With each client-server interaction:</p><ul><li><p>There is a client process, and that process has a process id (PID).</p></li><li><p>There is a server process, which also has a PID.</p></li><li><p>Requests originate from the client and are sent as TCP/IP packets to the server.</p></li><li><p>Responses originate from the server and are sent as TCP/IP packets to the client.</p></li><li><p>The client finds the server via an IP address and TCP port. That port is the one we tend to be aware of as software engineers. When we configure a client we tell it the IP address &#8212; or indirectly, via a DNS name &#8212; and the port.</p></li><li><p>The server also finds the client via an IP address and TCP port. We don&#8217;t configure the information into the server, so how does it know about the client?</p></li></ul><p>The answer is in the IP and TCP headers for each packet.</p><ul><li><p>IP headers contain several fields, which include both source and destination IP addresses.</p></li><li><p>TCP headers also contain several fields, including both source and destination TCP ports.</p></li><li><p>The way a server knows where the client is located is by extracting those fields when a request is received, and using them when it generates a response.</p></li><li><p>While the server port resulted from static configuration, client ports are often determined on-the-fly and sometimes referred to as &#8220;ephemeral&#8221; ports.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WbOS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WbOS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png 424w, https://substackcdn.com/image/fetch/$s_!WbOS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png 848w, https://substackcdn.com/image/fetch/$s_!WbOS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png 1272w, https://substackcdn.com/image/fetch/$s_!WbOS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WbOS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png" width="383" height="641" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:641,&quot;width&quot;:383,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51181,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WbOS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png 424w, https://substackcdn.com/image/fetch/$s_!WbOS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png 848w, https://substackcdn.com/image/fetch/$s_!WbOS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png 1272w, https://substackcdn.com/image/fetch/$s_!WbOS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35be141f-6ea2-4489-af1b-ba41bdfcd1c1_383x641.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There is information missing from these packets: the PIDs. Nothing in the network traffic knows about processes. That association is handled by operating system support which the application processes make use of.</p><ul><li><p>The client process will use a socket and bind it to the client IP address and ephemeral TCP port.</p></li><li><p>The server process will use a socket and bind it to the server IP address and statically-configured TCP port.</p></li></ul><h2>Ports and PostgreSQL</h2><p>The picture changes when you introduce a PostgreSQL service into the equation. Since TCP/IP packets don&#8217;t know anything about processes, there is no reason to restrict ourselves to a single server process for the implementation.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!l2-a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!l2-a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png 424w, https://substackcdn.com/image/fetch/$s_!l2-a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png 848w, https://substackcdn.com/image/fetch/$s_!l2-a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png 1272w, https://substackcdn.com/image/fetch/$s_!l2-a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!l2-a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png" width="591" height="281" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:281,&quot;width&quot;:591,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:40844,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!l2-a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png 424w, https://substackcdn.com/image/fetch/$s_!l2-a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png 848w, https://substackcdn.com/image/fetch/$s_!l2-a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png 1272w, https://substackcdn.com/image/fetch/$s_!l2-a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ee44fe8-570e-4376-9c9b-fb5b61916c81_591x281.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>PostgreSQL has several processes, but for this discussion we are concerned with two:</p><ul><li><p>The Postmaster is the process that listens to that statically-configured port known for establishing connections. The default is 5432.</p></li><li><p>For each client connection the Postmaster will identify or launch a Backend process to do the work the client is requesting. The Backend is the process that will use the client IP address and ephemeral port for responding with the results. </p></li></ul><p>There are more complicated processing scenarios in PostgreSQL but we&#8217;ll ignore them for today. What is shown here accounts for what is typical.</p><p>We&#8217;re simplifying the picture a bit here as clients and backends can continue this back-and-forth between requests and responses. The Backend is delegated to deal with both once the Postmaster finishes accepting the incoming client connection. The Postmaster will go back to listening for fresh connection attempts.</p><p>Note that 5432 is not an entirely arbitrary choice of number. There is an official <a href="https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=5432">IANA registration for the service name and port</a>. While you can configure PostgreSQL to use other ports, by sticking with 5432 you will find some network-aware Linux tools will automatically know that &#8220;5432&#8221; and &#8220;postgresql&#8221; are interchangeable.</p><h2>Gathering Client Connection Data</h2><p>All of this connection and request/response activity isn&#8217;t just hidden away in the memory state of the various processes. We can examine it. Some of that examination can happen within PostgreSQL itself, while some needs to be done with O/S tools. </p><p>We&#8217;ll explore this <a href="https://substack.the-experimentalist.com/p/postgresql-17-on-vagrant-and-virtualbox">via the Vagrant/VirtualBox VM from the previous article</a>. Refer to that if you haven&#8217;t already spun up your own PostgreSQL sandbox.</p><pre><code># cd to the project directory with the Vagrantfile before proceeding
vagrant up --provision
vagrant ssh</code></pre><p>Now we can use a <strong>psql</strong> session to do some looking around. We&#8217;ll use the <strong>vagrant</strong> database and username as we previously created that as a superuser. As a memory-jogger we just used the trivial password &#8220;vagrant&#8221; for that sandbox. We&#8217;ll get into the details of database authentication and security in future articles.</p><pre><code>psql --host=localhost --dbname=vagrant --username=vagrant</code></pre><p>You don&#8217;t have to specify the dbname as it is the one associated with the username, but it&#8217;s worth seeing an example of how to when you need to.</p><p>The ability to examine client connection activity is handled in PostgreSQL via the <a href="https://www.postgresql.org/docs/17/monitoring-stats.html">Cumulative Statistics System</a>. Under-the-hood PostgreSQL uses internal tables to gather activity data which you then examine via pre-defined &#8220;system views&#8221;. We&#8217;re going to put one of them to work: <a href="https://www.postgresql.org/docs/17/monitoring-stats.html#MONITORING-PG-STAT-ACTIVITY-VIEW">pg_stat_activity</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PcVl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PcVl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png 424w, https://substackcdn.com/image/fetch/$s_!PcVl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png 848w, https://substackcdn.com/image/fetch/$s_!PcVl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png 1272w, https://substackcdn.com/image/fetch/$s_!PcVl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PcVl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png" width="1456" height="576" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:576,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:225271,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PcVl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png 424w, https://substackcdn.com/image/fetch/$s_!PcVl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png 848w, https://substackcdn.com/image/fetch/$s_!PcVl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png 1272w, https://substackcdn.com/image/fetch/$s_!PcVl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07173d70-19d3-4ae3-9252-d66b7d435437_2048x810.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Our <strong>psql</strong> session is the only client we currently have connected (as shown by the backend_type being '&#8220;client backend&#8221;), and we can see it there in the response. As we are running on the same host as the database service and connected via &#8220;localhost&#8221;, our client IP address is correctly 127.0.0.1 as shown for client_addr. The client-side ephemeral port for the connection is 41592 as shown for client_port.</p><p>Notice the &#8220;pid&#8221; column. What is that 10614 process id for? Let&#8217;s look at the PostgreSQL processes just like we did in the previous article:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XpFc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XpFc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png 424w, https://substackcdn.com/image/fetch/$s_!XpFc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png 848w, https://substackcdn.com/image/fetch/$s_!XpFc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png 1272w, https://substackcdn.com/image/fetch/$s_!XpFc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XpFc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png" width="1456" height="542" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/af0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:542,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:317040,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XpFc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png 424w, https://substackcdn.com/image/fetch/$s_!XpFc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png 848w, https://substackcdn.com/image/fetch/$s_!XpFc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png 1272w, https://substackcdn.com/image/fetch/$s_!XpFc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faf0fb535-8b11-44db-8e81-dd5ec74f5c74_2048x762.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This has one more row than was shown in the previous article. That bottom row is the corresponding backend for our <strong>psql</strong> session. The &#8220;pid&#8221; shown in <strong>psql</strong> thus was for the backend process, not the client process. The <strong>ps</strong> output even shows the client IP address and ephemeral port that particular backend is handling.</p><p>Also compare the first entry of the <strong>ps</strong> results to our query response. That process doesn&#8217;t show in <strong>psql</strong> at all! That&#8217;s because it is the parent process with the Postmaster functionality. It doesn&#8217;t connect to the database, it is the thing that knows connections are something to be managed along with the processing resources assigned to them. As it doesn&#8217;t connect to directly perform database work, pg_stat_activity doesn&#8217;t know about it.</p><p>We only have that single <strong>psql</strong> session so it&#8217;s trivial to identify the client. We&#8217;re going to increase the difficulty by launching two more clients. One will be in another <strong>psql</strong> session on the same VM, and the other will be a <strong>psql</strong> session started from the underlying host that the VM is running on. As a reminder <a href="https://substack.the-experimentalist.com/p/postgresql-17-on-vagrant-and-virtualbox">from the last article</a>, we configured pg_hba.conf to allow connections via the mask 10.0.2.0/24 and here you are seeing why: the VM&#8217;s virtual network mapped the host to an address in that range.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!08r8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!08r8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png 424w, https://substackcdn.com/image/fetch/$s_!08r8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png 848w, https://substackcdn.com/image/fetch/$s_!08r8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png 1272w, https://substackcdn.com/image/fetch/$s_!08r8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!08r8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png" width="1456" height="678" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:678,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:258443,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!08r8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png 424w, https://substackcdn.com/image/fetch/$s_!08r8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png 848w, https://substackcdn.com/image/fetch/$s_!08r8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png 1272w, https://substackcdn.com/image/fetch/$s_!08r8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F979110ed-fe21-4714-894c-c35bd12fbf06_2048x954.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now we have three <strong>psql</strong> &#8220;client backend&#8221; sessions and no real way to tell between them. How do we figure out which client matches up with which backend? All we have are the backend process ids, but that tells us nothing about each client.</p><p>Remember earlier we noted that sockets are what associate processes with IP addresses and ports? That&#8217;s how we figure can this out. In the previous article one of the provisioning steps installed the iproute2 package, which provides us with <strong>ss</strong>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!L8vf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!L8vf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png 424w, https://substackcdn.com/image/fetch/$s_!L8vf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png 848w, https://substackcdn.com/image/fetch/$s_!L8vf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png 1272w, https://substackcdn.com/image/fetch/$s_!L8vf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!L8vf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png" width="1456" height="798" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:798,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:398267,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!L8vf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png 424w, https://substackcdn.com/image/fetch/$s_!L8vf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png 848w, https://substackcdn.com/image/fetch/$s_!L8vf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png 1272w, https://substackcdn.com/image/fetch/$s_!L8vf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87407081-6610-424f-aa4c-2aa643c27e61_2048x1122.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This shows us both the client and server connections that are on the VM.</p><ul><li><p>You can ignore the &#8220;sshd&#8221; rows, those are for the terminal sessions I created.</p></li><li><p><strong>sudo ss -tunp</strong> ensures we see the sockets and corresponding process data for all users.</p></li><li><p><strong>ss -tunp</strong> (i.e. no use of <strong>sudo</strong>, running as <strong>vagrant</strong>); would have only shown &#8220;Process&#8221; column details for the client process sockets of the current user. </p></li><li><p><strong>sudo -U postgres ss -tunp</strong> would have only shown &#8220;Process&#8221; column details for the backend process sockets. The backend processes run as the <strong>postgres</strong> user.</p></li><li><p>None of these shows the client details for the <strong>psql</strong> session launched from outside the VM. The only way to know client details is by doing this form of examination on the device corresponding to the client IP address.</p></li><li><p>The two entries with &#8220;psql&#8221; are the clients running on the VM. Each shows the &#8220;Local Address:Port&#8221; and corresponds to the &#8220;client_addr,client_port&#8221; columns shown earlier in the pg_stat_activity response. The PID showing is for the client process, not the backend.</p></li><li><p>The three entries with &#8220;postgres&#8221; are the backends running on the VM. Each has the PID that corresponds to the &#8220;pid&#8221; column shown earlier in the pg_stat_activity response. The &#8220;Peer Address:Port&#8221; corresponds to the client IP and port that backend will respond to.</p></li></ul><p>As a quick aside, the &#8220;postgres&#8221; backend for the remote (host) psql client looks a little different than the other two cases. That <strong>psql</strong> client is not connecting via localhost, it is connecting via an IP address assigned by the VM&#8217;s virtual network. We can confirm that &#8220;10.0.2.15&#8221; is indeed the IP address assigned to the VM:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qyfU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qyfU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png 424w, https://substackcdn.com/image/fetch/$s_!qyfU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png 848w, https://substackcdn.com/image/fetch/$s_!qyfU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png 1272w, https://substackcdn.com/image/fetch/$s_!qyfU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qyfU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png" width="1456" height="392" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:392,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:167466,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qyfU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png 424w, https://substackcdn.com/image/fetch/$s_!qyfU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png 848w, https://substackcdn.com/image/fetch/$s_!qyfU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png 1272w, https://substackcdn.com/image/fetch/$s_!qyfU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bd75153-5493-48e0-920a-60f9dc454fd4_1940x522.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We are almost there.</p><ul><li><p>Get the &#8220;Local Address:Port&#8221; on the &#8220;psql&#8221; rows.</p></li><li><p>Get the &#8220;Peer Address:Port&#8221; on the &#8220;postgres&#8221; rows.</p></li><li><p>Match them up. In database terms, do a join on (IP, Port) across both subsets.</p></li><li><p>Get the &#8220;pid&#8221; value from the &#8220;Process&#8221; columns. There will be two per match-up, one for the client and one for the backend.</p></li></ul><pre><code>|-------------|----------------|-----------------|------------|
| Backend PID |   Backend Addr |     Client Addr | Client PID |
|-------------|----------------|-----------------|------------|
|       10614 | 127.0.0.1:5432 | 127.0.0.1:41592 |      10612 |
|       10730 | 127.0.0.1:5432 | 127.0.0.1:47160 |      10737 |
|       10741 | 10.0.2.15:5432 |  10.0.2.2:54092 |          ? |
|-------------|----------------|-----------------|------------|</code></pre><p>The final case is a little different because I&#8217;m running VirtualBox on Windows. For that we need to run netstat in a terminal window with privilege escalation:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SjU0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SjU0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png 424w, https://substackcdn.com/image/fetch/$s_!SjU0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png 848w, https://substackcdn.com/image/fetch/$s_!SjU0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png 1272w, https://substackcdn.com/image/fetch/$s_!SjU0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SjU0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png" width="1456" height="752" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:752,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:194090,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170953957?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SjU0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png 424w, https://substackcdn.com/image/fetch/$s_!SjU0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png 848w, https://substackcdn.com/image/fetch/$s_!SjU0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png 1272w, https://substackcdn.com/image/fetch/$s_!SjU0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd513cbe9-378e-4fba-b5ed-8ce86f4bb762_1552x802.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>That gives us similar information for the third client, including how the backend connection is being proxied by VirtualBox. The PID for the proxy won&#8217;t correspond to anything within the VM and can be ignored.</p><p>We now have a client-process-to-backend-process correspondence! Completing the table required using either <strong>netstat</strong> or <strong>ss</strong> and having access to the appropriate client host, as identified by &#8220;client_addr&#8221; in pg_stat_activity. Discovering the client PIDs allows us to investigate those processes on those hosts. We weren&#8217;t even constrained to only searching on Linux.</p><p>Could you automate all this? Yes, assuming the client hosts are accessible to you. That&#8217;s a discussion for another day.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.the-experimentalist.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Experimentalist is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><a href="https://substack.the-experimentalist.com/p/client-connectivity-with-postgresql">The Experimentalist : Client Connectivity with PostgreSQL</a> &#169; 2025 by <a href="https://www.linkedin.com/in/reidmpinchback/">Reid M. Pinchback</a> is licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a></p>]]></content:encoded></item><item><title><![CDATA[PostgreSQL 17 on Vagrant and VirtualBox]]></title><description><![CDATA[Create a sandbox to explore how this database is put together]]></description><link>https://substack.the-experimentalist.com/p/postgresql-17-on-vagrant-and-virtualbox</link><guid isPermaLink="false">https://substack.the-experimentalist.com/p/postgresql-17-on-vagrant-and-virtualbox</guid><dc:creator><![CDATA[Reid M. Pinchback]]></dc:creator><pubDate>Thu, 14 Aug 2025 03:58:38 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!d5CR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!d5CR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!d5CR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg 424w, https://substackcdn.com/image/fetch/$s_!d5CR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg 848w, https://substackcdn.com/image/fetch/$s_!d5CR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!d5CR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!d5CR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg" width="514" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:514,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:356002,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!d5CR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg 424w, https://substackcdn.com/image/fetch/$s_!d5CR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg 848w, https://substackcdn.com/image/fetch/$s_!d5CR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!d5CR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e1ff51a-e321-4f05-a5a0-9fb5d4560c3c_514x400.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the <a href="https://substack.the-experimentalist.com/p/building-a-swe-experimentation-platform">article series on an exploratory lab</a> I&#8217;ll eventually tackle provisioning of container and virtualization-based sandboxes. To tease at the benefits, we&#8217;re going to use a Vagrant-VirtualBox setup to install and poke around a running database.</p><p>If you aren&#8217;t already using them, you can download and install in order:</p><ul><li><p><a href="https://www.virtualbox.org/wiki/Downloads">VirtualBox &#8594; https://www.virtualbox.org/wiki/Downloads</a></p></li><li><p><a href="https://developer.hashicorp.com/vagrant/install">Vagrant &#8594; https://developer.hashicorp.com/vagrant/install</a></p></li></ul><div><hr></div><p><strong>Next:</strong> <strong><a href="https://substack.the-experimentalist.com/p/client-connectivity-with-postgresql">Client Connectivity with PostgreSQL</a></strong></p><div><hr></div><p>Project files for the article are available from the newsletter&#8217;s GitLab repository:</p><ul><li><p><strong><a href="https://gitlab.com/The-Experimentalist/article_support_2025/-/blob/main/article/2025-08-13-postgresql-17-on-vagrant-and-virtualbox/Vagrantfile?ref_type=heads">Vagrantfile</a></strong></p></li><li><p><strong><a href="https://gitlab.com/The-Experimentalist/article_support_2025/-/blob/main/article/2025-08-13-postgresql-17-on-vagrant-and-virtualbox/provisions/postgresql.sh?ref_type=heads">provisions/postgresql.sh</a></strong></p></li></ul><p>Just create a project that maintains the directory structure. Once you&#8217;ve done that and installed both VirtualBox and Vagrant:</p><ul><li><p><strong>cd</strong> to the project directory</p></li><li><p><strong>vagrant up</strong></p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mbd3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mbd3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png 424w, https://substackcdn.com/image/fetch/$s_!mbd3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png 848w, https://substackcdn.com/image/fetch/$s_!mbd3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png 1272w, https://substackcdn.com/image/fetch/$s_!mbd3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mbd3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png" width="1456" height="769" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:769,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:383179,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mbd3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png 424w, https://substackcdn.com/image/fetch/$s_!mbd3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png 848w, https://substackcdn.com/image/fetch/$s_!mbd3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png 1272w, https://substackcdn.com/image/fetch/$s_!mbd3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8989aa86-d0d8-4195-a8fc-8fb4b7a26f3b_2048x1082.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This will get the ball rolling. If the install will go slowly, it&#8217;ll usually be right after generating the SSH key. That tends to happen if there is some difficulty in mapping hardware functionality that the O/S expects to what VirtualBox is able to provide. I&#8217;ve found <a href="https://portal.cloud.hashicorp.com/vagrant/discover/bento/ubuntu-22.04">the Ubuntu 22.04 Bento box</a> to work pretty smoothly. It may take 10-20 minutes to download an O/S image, build a VM from it, and install PostgreSQL, and the install will conclude with:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Urz6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Urz6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png 424w, https://substackcdn.com/image/fetch/$s_!Urz6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png 848w, https://substackcdn.com/image/fetch/$s_!Urz6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png 1272w, https://substackcdn.com/image/fetch/$s_!Urz6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Urz6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png" width="1456" height="855" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:855,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:396924,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Urz6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png 424w, https://substackcdn.com/image/fetch/$s_!Urz6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png 848w, https://substackcdn.com/image/fetch/$s_!Urz6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png 1272w, https://substackcdn.com/image/fetch/$s_!Urz6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1276dbb-b0df-4f8a-b408-8e45d0a2e4b3_2048x1202.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Any future restarts of the box will be much faster. At this point you have:</p><ul><li><p>A running Ubuntu 22.04 VM.</p></li><li><p>A running PostgreSQL 17 service.</p></li><li><p>An Ubuntu login account named &#8220;<strong>vagrant</strong>&#8221; you can <strong>ssh</strong> to.</p></li><li><p>An PostgreSQL user named &#8220;<strong>vagrant</strong>&#8221; with the password &#8220;<strong>vagrant</strong>&#8221;.</p></li><li><p>The ability to connect to the database as that user with either a client running within the VM, or from a client on the underlying host operating system.</p></li></ul><p>Let&#8217;s take a look at how that came about. The driver of the bus is the <strong>Vagrantfile</strong>:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q3yZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q3yZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png 424w, https://substackcdn.com/image/fetch/$s_!Q3yZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png 848w, https://substackcdn.com/image/fetch/$s_!Q3yZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png 1272w, https://substackcdn.com/image/fetch/$s_!Q3yZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q3yZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png" width="1456" height="739" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:739,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:256702,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Q3yZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png 424w, https://substackcdn.com/image/fetch/$s_!Q3yZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png 848w, https://substackcdn.com/image/fetch/$s_!Q3yZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png 1272w, https://substackcdn.com/image/fetch/$s_!Q3yZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9fee794c-7f1f-4fdd-aaf0-774b05d6b6e4_1974x1002.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is the file from the GitLab repository but with comments stripped out. Let&#8217;s walk through the key parts.</p><p>Specify the O/S image you want for the VM:</p><pre><code>config.vm.box = "bento/ubuntu-22.04"
config.vm.box_version = "202508.03.0"</code></pre><p>PostgreSQL by default listens on port 5432. We want to make it possible for a database client running on the host to connect to the database service within the VM on that same port number:</p><pre><code>config.vm.network "forwarded_port", guest: 5432, host: 5432, \
  host_ip: "127.0.0.1"</code></pre><p>We specify that the provider is VirtualBox, but if you happen to already use Hyper-V or VMware, this is where you would make the change to use them instead:</p><pre><code>config.vm.provider "virtualbox" do |vb|</code></pre><p>Finally we have a script to run as <strong>root</strong> for provisioning the VM once it is ready. Ours sets up PostgreSQL and does other assorted housekeeping:</p><pre><code>config.vm.provision "shell", name: "postgresql", \
  path: "provisions/postgresql.sh", privileged: true</code></pre><p>All of the real magic happens in the provisioning script. First we set up the firewall and install some utilities that may come in handy later:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YUGg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YUGg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png 424w, https://substackcdn.com/image/fetch/$s_!YUGg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png 848w, https://substackcdn.com/image/fetch/$s_!YUGg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png 1272w, https://substackcdn.com/image/fetch/$s_!YUGg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YUGg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png" width="1456" height="941" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:941,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:244411,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YUGg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png 424w, https://substackcdn.com/image/fetch/$s_!YUGg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png 848w, https://substackcdn.com/image/fetch/$s_!YUGg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png 1272w, https://substackcdn.com/image/fetch/$s_!YUGg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde685877-84e6-4204-ad1a-09af140aeb99_1736x1122.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Then we install the APT repository for PostgreSQL, and install the service:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!28lh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!28lh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png 424w, https://substackcdn.com/image/fetch/$s_!28lh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png 848w, https://substackcdn.com/image/fetch/$s_!28lh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png 1272w, https://substackcdn.com/image/fetch/$s_!28lh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!28lh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png" width="1456" height="571" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:571,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:169927,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!28lh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png 424w, https://substackcdn.com/image/fetch/$s_!28lh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png 848w, https://substackcdn.com/image/fetch/$s_!28lh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png 1272w, https://substackcdn.com/image/fetch/$s_!28lh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa5a239f5-f975-4f33-8ce6-873d0d6b2b27_1534x602.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The next step is a bit more packed and needs explaining. </p><p>We want to configure the database to have an initial user and database schema to work in. Vagrant allows us to re-run provisioning scripts. Those scripts should be idempotent so they don&#8217;t fail they next time they are run. A zero-byte file named <strong>.configured</strong> is created as a marker to note we&#8217;ve already been here before.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qCd1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qCd1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png 424w, https://substackcdn.com/image/fetch/$s_!qCd1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png 848w, https://substackcdn.com/image/fetch/$s_!qCd1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png 1272w, https://substackcdn.com/image/fetch/$s_!qCd1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qCd1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png" width="1456" height="821" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:821,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:314471,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qCd1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png 424w, https://substackcdn.com/image/fetch/$s_!qCd1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png 848w, https://substackcdn.com/image/fetch/$s_!qCd1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png 1272w, https://substackcdn.com/image/fetch/$s_!qCd1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f12d522-65f0-4a5b-9ec1-c37cac4ea271_1990x1122.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The work of the provisioning on the first pass consists of:</p><ul><li><p>Change <strong>postgresql.conf</strong> to listen for connections coming in from any host. This is done so that it doesn&#8217;t just respond to VM clients, it would also respond to the any client the Vagrant configuration allows access to.</p></li><li><p>Create a user named <strong>vagrant</strong>, which for simplicity here I&#8217;ve made a superuser just like the default user <strong>postgres</strong> already is.</p></li><li><p>Provide the <strong>vagrant</strong> user with a password to allow login. Password-based authentication is not the only option, but we&#8217;re going for simple.</p></li><li><p>Create a database for the new user and give it the same name as that user.</p></li><li><p>Change <strong>pg_hba.conf</strong> to allow the new user to have access. We specify both the loopback address to allow login from within the VM, and an address mask suited to any connections over eth0 with IP addresses assigned to the VM or host.</p></li><li><p>Restart the database service once everything is ready. If this was a production instance I would dig into the docs to remind me which <strong>postgresql.conf</strong> features require a restart versus a config reload. For here this works fine, and I want a chance to confirm the systemd unit for the service in any case.</p></li></ul><p>Some of the steps are wrapped in a heredoc to run as the <strong>postgres</strong> user. We are using nested heredocs: the outer level to process steps as <strong>postgres</strong>, and the inner level to specify content to append to the <strong>pg_hba.conf</strong> file. The entire provisioning script runs as a privileged user by Vagrant because we said so in the <strong>Vagrantfile</strong>, which for us means as <strong>root</strong>. The PostgreSQL service runs as <strong>postgres</strong>.</p><p>Finally, the provisioning script cleans up any dangling APT state. If anything installed flagged a need to reboot the host (in this case, the VM) to complete an installation, then it gets handled now.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AEXC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AEXC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png 424w, https://substackcdn.com/image/fetch/$s_!AEXC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png 848w, https://substackcdn.com/image/fetch/$s_!AEXC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png 1272w, https://substackcdn.com/image/fetch/$s_!AEXC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AEXC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png" width="944" height="562" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:562,&quot;width&quot;:944,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:102480,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AEXC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png 424w, https://substackcdn.com/image/fetch/$s_!AEXC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png 848w, https://substackcdn.com/image/fetch/$s_!AEXC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png 1272w, https://substackcdn.com/image/fetch/$s_!AEXC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc71a17c1-eba9-4aa1-9b6f-831ac57f8596_944x562.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We now have the ability to <strong>ssh</strong> into the running VM. We use <strong>vagrant ssh</strong> for that, because it knows about the key pair that Vagrant generated when the VM was created.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FQed!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FQed!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png 424w, https://substackcdn.com/image/fetch/$s_!FQed!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png 848w, https://substackcdn.com/image/fetch/$s_!FQed!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png 1272w, https://substackcdn.com/image/fetch/$s_!FQed!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FQed!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png" width="1456" height="1140" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1140,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:316803,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FQed!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png 424w, https://substackcdn.com/image/fetch/$s_!FQed!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png 848w, https://substackcdn.com/image/fetch/$s_!FQed!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png 1272w, https://substackcdn.com/image/fetch/$s_!FQed!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24cd89c2-df6c-4526-80ae-b03e900f9a9f_1586x1242.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As a final step, we&#8217;ll confirm that the database service processes exist:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GM6-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GM6-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png 424w, https://substackcdn.com/image/fetch/$s_!GM6-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png 848w, https://substackcdn.com/image/fetch/$s_!GM6-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png 1272w, https://substackcdn.com/image/fetch/$s_!GM6-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GM6-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png" width="1456" height="485" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:485,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:272514,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://substack.the-experimentalist.com/i/170936087?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GM6-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png 424w, https://substackcdn.com/image/fetch/$s_!GM6-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png 848w, https://substackcdn.com/image/fetch/$s_!GM6-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png 1272w, https://substackcdn.com/image/fetch/$s_!GM6-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe91ef65b-72da-4924-84df-ca412a4fc199_2048x682.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the next article we&#8217;ll start exploring the service in more detail.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://substack.the-experimentalist.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Experimentalist is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p><a href="https://substack.the-experimentalist.com/p/postgresql-17-on-vagrant-and-virtualbox">The Experimentalist : PostgreSQL 17 on Vagrant and VirtualBox</a> &#169; 2025 by <a href="https://www.linkedin.com/in/reidmpinchback/">Reid M. Pinchback</a> is licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a></p>]]></content:encoded></item></channel></rss>