<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Satwik Sai Prakash Sahoo</title>
    <description>The latest articles on DEV Community by Satwik Sai Prakash Sahoo (@satwiksps).</description>
    <link>https://dev.to/satwiksps</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3961574%2Fb848de46-61a0-4598-b0be-053b4b8b5dca.jpeg</url>
      <title>DEV Community: Satwik Sai Prakash Sahoo</title>
      <link>https://dev.to/satwiksps</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/satwiksps"/>
    <language>en</language>
    <item>
      <title>Wrapping Up My GSoC 2026 Journey with sbi</title>
      <dc:creator>Satwik Sai Prakash Sahoo</dc:creator>
      <pubDate>Mon, 24 Aug 2026 02:31:36 +0000</pubDate>
      <link>https://dev.to/satwiksps/wrapping-up-my-gsoc-2026-journey-with-sbi-3gp2</link>
      <guid>https://dev.to/satwiksps/wrapping-up-my-gsoc-2026-journey-with-sbi-3gp2</guid>
      <description>&lt;p&gt;This is my final work product for &lt;a href="https://summerofcode.withgoogle.com/programs/2026/projects/P5QOhl9F" rel="noopener noreferrer"&gt;Google Summer of Code 2026&lt;/a&gt;, where I spent the summer redesigning how &lt;a href="https://github.com/sbi-dev/sbi" rel="noopener noreferrer"&gt;sbi&lt;/a&gt; builds neural networks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the project is about?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sbi&lt;/code&gt; does Bayesian inference for simulators you cannot write a likelihood for. You have a simulator and a prior, you run simulations, and a neural network learns the posterior over the simulator's parameters from those runs. It implements a whole family of methods (NPE, NLE, NRE, FMPE, NPSE and the mixed variants), and every one of them trains some kind of neural density estimator.&lt;/p&gt;

&lt;p&gt;My project was about how you pick that network and configure it. Over the summer I replaced the old string and factory-function interface with typed configuration objects, one class per model, across every estimator family in the library. The goal was that a setting the chosen model cannot use fails immediately when you write it, rather than being quietly dropped on the way to the network, and that existing code keeps working through a deprecation path instead of breaking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Organisation:&lt;/strong&gt; &lt;a&gt;NumFOCUS&lt;/a&gt; | &lt;strong&gt;Sub-organisation:&lt;/strong&gt; &lt;a href="https://github.com/sbi-dev/sbi" rel="noopener noreferrer"&gt;sbi-dev/sbi&lt;/a&gt; | &lt;strong&gt;Mentors:&lt;/strong&gt; Jan Teusen (&lt;a href="https://github.com/janfb" rel="noopener noreferrer"&gt;@janfb&lt;/a&gt;) &amp;amp; Nicholas Junge (&lt;a href="https://github.com/nicholasjng" rel="noopener noreferrer"&gt;@nicholasjng&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  What was the problem?
&lt;/h2&gt;

&lt;p&gt;The old API had three ways to specify a network, and they had grown separately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;string&lt;/strong&gt;: &lt;code&gt;NPE(prior, density_estimator="nsf")&lt;/code&gt;. Easy, but no way to set hyperparameters.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;factory function&lt;/strong&gt;: &lt;code&gt;posterior_nn(model="nsf", hidden_features=64)&lt;/code&gt;. It returned an opaque closure. You could not inspect it, print it or serialize it, and it accepted any keyword you gave it.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;custom module&lt;/strong&gt;, for full control.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The factory functions were the problem. They spanned a whole family of models, so their signature was the union of every setting any model might want. A setting that the chosen model did not use was quietly discarded. There was no type information anywhere, so editors could not autocomplete and type checkers could not help.&lt;/p&gt;

&lt;p&gt;The goal was a layered API where each layer is typed and inspectable, mistakes fail at configuration time instead of at training time, and existing code keeps working through a deprecation path rather than breaking.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is the problem solved now?
&lt;/h2&gt;

&lt;p&gt;Every estimator family now has typed config objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sbi.inference&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NRE&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sbi.neural_nets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZukoNSFConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ResNetClassifierConfig&lt;/span&gt;

&lt;span class="n"&gt;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NPE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ZukoNSFConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hidden_features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_transforms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NRE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ResNetClassifierConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hidden_features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_blocks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They are frozen dataclasses, so they print cleanly, you can log them in an experiment record, and your editor autocompletes the fields. Strings still work and emit a &lt;code&gt;FutureWarning&lt;/code&gt; that names the class to switch to. Custom modules still work.&lt;/p&gt;

&lt;p&gt;The part I am most happy with is what happens when you are wrong. There are three distinct failures, all before training:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;NSFConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hiden_features&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# TypeError  - misspelled name
&lt;/span&gt;&lt;span class="nc"&gt;NSFConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_score_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strucured&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ValueError - misspelled value
&lt;/span&gt;&lt;span class="nc"&gt;MAFConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_bins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;# TypeError  - not a setting this model has
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What each PR did
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1872" rel="noopener noreferrer"&gt;#1872&lt;/a&gt; laid the groundwork.&lt;/strong&gt; The shared base class, the supporting types, and a protocol rename to free up the name "Builder" for the new objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1877" rel="noopener noreferrer"&gt;#1877&lt;/a&gt; added the first real builder&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1882" rel="noopener noreferrer"&gt;#1882&lt;/a&gt; wired it into the NPE trainers&lt;/strong&gt;, with the deprecation path for strings.&lt;/p&gt;

&lt;p&gt;The most useful thing that happened here was something we removed. The original design had &lt;code&gt;build()&lt;/code&gt; take a &lt;code&gt;BuildContext&lt;/code&gt; object carrying shape, device and z-scoring statistics. While implementing it, Jan noticed the parameter was never actually read: everything it held could be derived from the training batch that was already being passed in. We cut it. It was a premature abstraction, and recognising that early saved a lot of machinery that would have needed maintaining.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1904" rel="noopener noreferrer"&gt;#1904&lt;/a&gt; extended the builder to NLE&lt;/strong&gt;, and this is where the design got interesting.&lt;/p&gt;

&lt;p&gt;NPE models the parameters given the data. NLE models the data given the parameters. Same builder, opposite roles. A signature of &lt;code&gt;build(batch_theta, batch_x)&lt;/code&gt; reads correctly for one and backwards for the other, which is exactly how you get a bug where the standardization lands on the wrong variable.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;build()&lt;/code&gt; became &lt;code&gt;build(batch_input, batch_condition)&lt;/code&gt;. &lt;code&gt;input&lt;/code&gt; is whatever the model is modelling and &lt;code&gt;condition&lt;/code&gt; is whatever it is conditioning on, and the trainer decides which is which. The config never has to know. That decision carried cleanly through every family that came after, including the vector-field one that landed months later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1912" rel="noopener noreferrer"&gt;#1912&lt;/a&gt; added the mixed density estimators&lt;/strong&gt; for MNLE and MNPE, where part of the data is discrete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;#1920&lt;/a&gt; added the NRE classifiers&lt;/strong&gt; and bundled in a set of fixes to the shared base class. The reasoning was timing: this was the first PR proving the base served a &lt;em&gt;third&lt;/em&gt; family, so fixing it there meant NRE and everything after inherited the fixes instead of needing a retrofit.&lt;/p&gt;

&lt;p&gt;What went in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invalid &lt;code&gt;Literal&lt;/code&gt; values now raise at construction.&lt;/strong&gt; A misspelled field &lt;em&gt;name&lt;/em&gt; already raised. A misspelled &lt;em&gt;value&lt;/em&gt; on a correctly named field did not, and stayed silent until training.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model-incompatible settings raise&lt;/strong&gt;, by inspecting the target build function's signature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;frozen=True&lt;/code&gt;&lt;/strong&gt; on the configs, so they are immutable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A custom &lt;code&gt;__repr__&lt;/code&gt;&lt;/strong&gt; that prints only what you actually set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uniform public API exposure&lt;/strong&gt;, since one builder had been left unexported.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also carried a rename I am glad we did. The configs used &lt;code&gt;z_score_x&lt;/code&gt; and &lt;code&gt;z_score_y&lt;/code&gt;, inherited from the low-level functions. Those names are ambiguous across families: for NPE, &lt;code&gt;z_score_x&lt;/code&gt; standardizes the &lt;em&gt;parameters&lt;/em&gt;, not &lt;code&gt;x&lt;/code&gt;. They became &lt;code&gt;z_score_input&lt;/code&gt; and &lt;code&gt;z_score_condition&lt;/code&gt;, matching the role-neutral signature. The builder API had not shipped in a release yet, so the rename was free. A few weeks later it would have been a breaking change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1921" rel="noopener noreferrer"&gt;#1921&lt;/a&gt; covered FMPE and NPSE.&lt;/strong&gt; This was also the point where the working style changed. Instead of implementing a design handed to me, I started writing a short design proposal first and getting it reviewed &lt;em&gt;before&lt;/em&gt; writing code. It felt slower for about a day and then obviously correct: a design mistake caught in a document costs an hour, and the same mistake caught in code review costs a week.&lt;/p&gt;

&lt;p&gt;Partway through, we changed direction, and I think it made the whole API better. Until then, each family had &lt;strong&gt;one flat class&lt;/strong&gt; holding every field any model in that family might want. To stop you setting a field your model does not use, I had built machinery: applicability lists, signature inspection, and drift tests to keep those lists honest as models changed.&lt;/p&gt;

&lt;p&gt;The new idea deletes all of it by making the class itself the choice. One small config per model, carrying only the fields that model accepts. Then there is nothing to validate, because a field that does not apply simply does not exist, and Python's own argument checking raises for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1975" rel="noopener noreferrer"&gt;#1975&lt;/a&gt; piloted that on the marginal trainer&lt;/strong&gt; with nine classes, one per Zuko flow. &lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1986" rel="noopener noreferrer"&gt;#1986&lt;/a&gt; then converted the density, classifier and mixed families&lt;/strong&gt;, replacing three flat builders with 15 density configs, 3 classifier configs and one mixed config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where things stand?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Merged and in sbi:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PR&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1872" rel="noopener noreferrer"&gt;#1872&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Foundation types and renames&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1877" rel="noopener noreferrer"&gt;#1877&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DensityEstimatorBuilder&lt;/code&gt; with &lt;code&gt;build()&lt;/code&gt; dispatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1882" rel="noopener noreferrer"&gt;#1882&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Integration into the NPE trainers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1904" rel="noopener noreferrer"&gt;#1904&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Integration into NLE, role-neutral signature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1912" rel="noopener noreferrer"&gt;#1912&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;MixedDensityEstimatorBuilder&lt;/code&gt; for MNLE and MNPE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;#1920&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RatioEstimatorBuilder&lt;/code&gt; for NRE, base class hardening, z-score rename&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1921" rel="noopener noreferrer"&gt;#1921&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;VectorFieldEstimatorBuilder&lt;/code&gt; for FMPE and NPSE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1975" rel="noopener noreferrer"&gt;#1975&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Per-model configs for &lt;code&gt;MarginalTrainer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1986" rel="noopener noreferrer"&gt;#1986&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Per-model configs for the density, classifier and mixed families&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Open for review:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/sbi-dev/sbi/pull/1997" rel="noopener noreferrer"&gt;#1997&lt;/a&gt; finishes the mixed family. The internal mixed build function was still taking two APIs at once, the old flat arguments and a config, so it now takes only a config and the deprecated strings build one in the factory. It also fixes a compatibility bug I would never have caught by counting parameters: the flat API passed a wider spline &lt;code&gt;tail_bound&lt;/code&gt; to every model that reads it, and keying that off the model name instead of the field silently narrowed the tail for one model while leaving the parameter count identical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Draft, queued behind each other:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/sbi-dev/sbi/pull/1996" rel="noopener noreferrer"&gt;#1996&lt;/a&gt; converts the vector-field family to the per-model design. This one is the most interesting of the set. The builder picks along what looks like three axes at once (flow or score matching, which SDE, which network architecture), but the axes are not independent: the estimator type and the SDE together select exactly one class. So it becomes two composed objects, &lt;code&gt;FlowMatchingConfig(net=MLPConfig(...))&lt;/code&gt;, which is seven classes instead of the sixteen you get from multiplying the axes out.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sbi-dev/sbi/pull/1987" rel="noopener noreferrer"&gt;#1987&lt;/a&gt; is the documentation PR: a new how-to for the config API, a rework of the "abstraction levels" guide, the config classes added to the API reference, and the deprecated strings cleared out of the tutorials. It stays a draft until #1996 merges, because it documents the API that PR lands, then it gets rebased and reviewed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stacking matters here. Each of these sits on the one before it, so they merge in order and each gets rebased onto &lt;code&gt;main&lt;/code&gt; once its predecessor lands. That is a habit I picked up the hard way, and it is why the diffs stay readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is left to do?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Test consolidation.&lt;/strong&gt; Each PR added its own test file, which was right during development and is now redundant. We agreed to consolidate them into fewer parametrized files once everything merges. The distinction that matters for CI cost is that tests which &lt;em&gt;train&lt;/em&gt; are the expensive ones, while construction and default checks are cheap, so the consolidation should keep the cheap ones broad and the expensive ones few.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retiring the legacy factory functions.&lt;/strong&gt; &lt;code&gt;posterior_nn&lt;/code&gt;, &lt;code&gt;likelihood_nn&lt;/code&gt; and &lt;code&gt;classifier_nn&lt;/code&gt; still exist and still work. They now build the new configs internally, so they are already thin. Once the deprecation window closes they can go, along with the legacy &lt;code&gt;*Config&lt;/code&gt; validators behind them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The stretch goal: data-source-agnostic builds.&lt;/strong&gt; This is the one I did not get to, and it is the most interesting remaining piece. Right now z-scoring statistics are computed inside the build functions from a single in-memory batch. For datasets too large to hold in one tensor, those statistics would have to come from a pre-pass over a dataloader, or be accumulated online. The plan is to move statistics and shapes out of the build functions and into a context object that the trainer fills in, which is the &lt;code&gt;BuildContext&lt;/code&gt; we deliberately deferred back in phase 1. The private types for it are already in the codebase as a reserved seam, so it is a local addition rather than a re-architecture. It is correctness-sensitive, since wrong z-scoring silently degrades inference rather than erroring, and it touches roughly 24 build functions, which is why it stayed a stretch goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgement
&lt;/h2&gt;

&lt;p&gt;I am really grateful to my mentor &lt;a href="https://github.com/janfb" rel="noopener noreferrer"&gt;Jan Teusen&lt;/a&gt; for giving me this opportunity and for reviews that consistently found the thing I had not thought about, and for pushing me to write designs before code in the second half. To &lt;a href="https://github.com/nicholasjng" rel="noopener noreferrer"&gt;Nicholas Junge&lt;/a&gt; for the backup mentorship. And to the sbi maintainers for treating a student's PRs with the same seriousness as anyone else's. This was a great learning experience for me.&lt;/p&gt;

&lt;p&gt;The coding period is over but the work is not. I am continuing with sbi through November to land the vector-field conversion and the documentation, and I intend to stick around after that as a permanent contributor.&lt;/p&gt;

&lt;p&gt;I have also written &lt;a href="https://dev.to/satwiksps"&gt;some blogs every two weeks throughout the program&lt;/a&gt; describing my learnings in detail.&lt;/p&gt;

&lt;p&gt;Signing off,&lt;br&gt;
Satwik Sai Prakash Sahoo&lt;br&gt;
&lt;a href="https://github.com/satwiksps" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; | &lt;a href="https://www.linkedin.com/in/satwiksps/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; | &lt;a href="https://bsky.app/profile/satwiksps.bsky.social" rel="noopener noreferrer"&gt;Bluesky&lt;/a&gt; | &lt;a href="https://x.com/satwiksps" rel="noopener noreferrer"&gt;X&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>opensource</category>
      <category>github</category>
      <category>google</category>
    </item>
    <item>
      <title>Final weeks of GSoC</title>
      <dc:creator>Satwik Sai Prakash Sahoo</dc:creator>
      <pubDate>Wed, 19 Aug 2026 12:50:31 +0000</pubDate>
      <link>https://dev.to/satwiksps/final-weeks-of-gsoc-4fjf</link>
      <guid>https://dev.to/satwiksps/final-weeks-of-gsoc-4fjf</guid>
      <description>&lt;p&gt;Hey everyone! Week 11 is done and week 12 is about to be over, which means the official GSoC coding period is ending soon. These two weeks brought a design change that I think makes the whole API better, a merged PR, and a couple of things about these flows that I only noticed because of the new design.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sbi-dev/sbi/pull/1921" rel="noopener noreferrer"&gt;PR #1921&lt;/a&gt; merged
&lt;/h2&gt;

&lt;p&gt;First, the vector field builder from my last post is in. FMPE and NPSE now go through the same typed interface as everything else, so four of the five families are done.&lt;/p&gt;

&lt;h2&gt;
  
  
  A change of direction: one class per model
&lt;/h2&gt;

&lt;p&gt;Up to this point every family had &lt;strong&gt;one flat builder class&lt;/strong&gt; holding every field any model in that family might want. &lt;code&gt;DensityEstimatorBuilder&lt;/code&gt; has fields for MAF, for NSF, for MDN, for MADE and for nine Zuko flows, all in one class. To stop people from setting a field their chosen model does not use, I had built machinery: applicability checks, whitelists, signature inspection, and drift tests to keep those lists honest.&lt;/p&gt;

&lt;p&gt;The new idea is to delete all of that by making the class itself the choice. One small config class per model, carrying only the fields that model actually accepts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;MarginalTrainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;MarginalNSFConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_transforms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a setting does not belong to that model, it is not a field on the class, so Python raises a &lt;code&gt;TypeError&lt;/code&gt; at construction. No whitelist, no applicability check, no drift test to keep in sync. Python's own argument checking does the job for free.&lt;/p&gt;

&lt;p&gt;This is was a major change in design decision, and &lt;a href="https://github.com/sbi-dev/sbi/pull/1975" rel="noopener noreferrer"&gt;PR #1975&lt;/a&gt; pilots it on the marginal trainer.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sbi-dev/sbi/pull/1975" rel="noopener noreferrer"&gt;PR #1975&lt;/a&gt;: per-model marginal configs
&lt;/h2&gt;

&lt;p&gt;So this PR adds nine classes, one for each unconditional Zuko flow: BPF, GF, MAF, NAF, NCSF, NICE, NSF, SOSPF and UNAF. The trainer default also moves from a string to &lt;code&gt;None&lt;/code&gt;, which resolves to &lt;code&gt;MarginalNSFConfig()&lt;/code&gt;, so that a plain &lt;code&gt;MarginalTrainer()&lt;/code&gt; does not trip the new deprecation warning. Strings still work and still warn.&lt;/p&gt;

&lt;p&gt;I also parametrized the tests over all nine models instead of testing one and hoping the rest behaved, which caught things I would have missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing nine classes made me look at nine models
&lt;/h2&gt;

&lt;p&gt;The thing I did not expect from this design is how much it forces you to actually read the model you are writing a class for. With one flat builder you write the union of every field and move on. With one class per model you have to answer, nine times, what does &lt;em&gt;this&lt;/em&gt; flow accept and what does it not.&lt;/p&gt;

&lt;p&gt;Two of them turned out to be genuinely different from the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gaussianization flows do not take a width.&lt;/strong&gt; &lt;code&gt;MarginalGFConfig&lt;/code&gt; has no &lt;code&gt;hidden_features&lt;/code&gt; field, which looks like an oversight until you follow where the value goes. Zuko routes the settings it does not name itself into an element-wise transform, and that transform only builds a network when there is a condition to build it from. A marginal flow is unconditional, so there is never a condition, so the network is never built and the width is dropped on the way there. Rather than accept a number and quietly ignore it, the class refuses it and points you at &lt;code&gt;num_transforms&lt;/code&gt; and &lt;code&gt;components&lt;/code&gt;, which are the settings that actually change the flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Circular splines do not live on the real line.&lt;/strong&gt; This one I found the hard way, through a CI failure. NCSF is a neural &lt;em&gt;circular&lt;/em&gt; spline flow, so its base distribution is uniform on&lt;br&gt;
&lt;code&gt;[-pi, pi]&lt;/code&gt; rather than a standard normal. My test was drawing &lt;code&gt;torch.randn&lt;/code&gt; samples for every model in the same loop, which is fine for eight of them and outside the domain for the ninth. The fix was a small helper that draws each model's data from the space it is actually defined on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_samples_in_domain_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ncsf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither of these is a bug in sbi. They are just facts about the models that the old flat class had no place to record, so nobody had to think about them. A per-model class has exactly the right place: the field list, and the docstring next to it.&lt;/p&gt;

&lt;p&gt;That is the part of this design I have come to like most. It is not only that wrong settings fail faster. It is that writing the classes made the differences between the models visible, to me while writing them and to the next person reading them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I am using AI on this project
&lt;/h2&gt;

&lt;p&gt;Something worth writing about, since it came up in both syncs.&lt;/p&gt;

&lt;p&gt;I used Claude Code for the first time for the marginal PR, and disclosed it in the PR description, which is now our standard practice. The workflow we settled on is that a second model reviews the first one's output adversarially. Claude writes, then Codex reviews it critically, and it is surprisingly good at finding bugs and pointing at code that is more verbose than it needs to be.&lt;/p&gt;

&lt;p&gt;But still the problem, I just started using Claude Code and although the code generation is fast, I sometimes have a hard time surfing around changed files to keep or remove AI generated code, the continuous changes and decisions somehow still take the same time when I wasn't using AI tools. But over the time I will get hold of it and finally it would increase my productivity. Jan also provided me some cool resources to learn Claude Code which I am very grateful for. Also the thing Jan said that stuck with me is that the early months of writing everything by hand, though slow, were the right foundation. The project understanding those months built is what makes the AI assisted work reviewable now. I can tell when a suggestion is wrong, and I have caught a few that were, including one that would have quietly swapped one flow implementation for a different one. &lt;/p&gt;

&lt;h2&gt;
  
  
  Tests and what happens now
&lt;/h2&gt;

&lt;p&gt;I raised a concern about CI cost, since we now have a test file per PR and they keep growing. The answer was a useful distinction: tests that &lt;em&gt;train&lt;/em&gt; are the expensive ones, while tests that just check defaults and construction are cheap. So the plan is to keep plenty of tests during development and decide before merge which ones to keep.&lt;/p&gt;

&lt;p&gt;The official coding period ends this week, and the documentation PR is my final contribution for the evaluation. But the work does not stop there. I am carrying on with sbi through November to implement our stretch goals which would make the user experience even better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;The density and classifier configs are in a draft PR, the documentation PR comes after that, and then the vector field conversion. Four families down, one to go.&lt;/p&gt;

&lt;p&gt;Thanks for sticking with me through the whole coding period, and see you in the next update!&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>python</category>
      <category>ai</category>
      <category>github</category>
    </item>
    <item>
      <title>Learnt Git Rebasing the hard way</title>
      <dc:creator>Satwik Sai Prakash Sahoo</dc:creator>
      <pubDate>Wed, 19 Aug 2026 12:49:35 +0000</pubDate>
      <link>https://dev.to/satwiksps/learnt-git-rebassing-the-hard-way-2dmd</link>
      <guid>https://dev.to/satwiksps/learnt-git-rebassing-the-hard-way-2dmd</guid>
      <description>&lt;p&gt;Hello again! Weeks 9 and 10 covered the fourth estimator family, the first release that carries my work, and a git mess that taught me more than the code did.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;PR #1920&lt;/a&gt; merged, and &lt;a href="https://github.com/sbi-dev/sbi/releases/tag/v0.27.0" rel="noopener noreferrer"&gt;sbi 0.27&lt;/a&gt; shipped
&lt;/h2&gt;

&lt;p&gt;First, the ratio estimator builder from my last post made it in, together with the whole base class hardening bundle. Right after that, &lt;a href="https://github.com/sbi-dev/sbi/releases/tag/v0.27.0" rel="noopener noreferrer"&gt;sbi 0.27&lt;/a&gt; was released and &lt;a href="https://github.com/sbi-dev/sbi/pull/1957" rel="noopener noreferrer"&gt;PRs 1 to 5 were ported over to &lt;code&gt;main&lt;/code&gt;&lt;/a&gt;. So the typed builder API is now actually in a release, for NPE, NLE, MNPE, MNLE and all four NRE variants.&lt;/p&gt;

&lt;p&gt;That also means the free rename window is closed. The &lt;code&gt;z_score_input&lt;/code&gt; and &lt;code&gt;z_score_condition&lt;/code&gt; names we picked in week 7 are now the shipped names, which is exactly why we did that rename when we did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the design before the code
&lt;/h2&gt;

&lt;p&gt;Under the new workflow I described last time, the vector field work started with a markdown file instead of a Python file.&lt;/p&gt;

&lt;p&gt;The vector field family is genuinely harder than the others. &lt;code&gt;build_vector_field_estimator&lt;/code&gt; picks along what looks like three axes at the same time: whether you want flow matching or score matching, which SDE type you want if it is score matching, and which network architecture sits inside. And unlike every other family, there are no per-model build functions to hang a config class on.&lt;/p&gt;

&lt;p&gt;I wrote up the options with some open questions then my mentor, &lt;a href="https://github.com/janfb" rel="noopener noreferrer"&gt;Jan&lt;/a&gt; reviewed it and we settled on one builder with a base class abstraction to cut the redundancy across builders. Doing this on paper first was clearly the right call. Some of the things I had assumed while writing the proposal turned out to be wrong, and finding that out in a review comment on a markdown file was a lot cheaper than finding it out in a review comment on 800 lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sbi-dev/sbi/pull/1921" rel="noopener noreferrer"&gt;PR #1921&lt;/a&gt;: &lt;code&gt;VectorFieldEstimatorBuilder&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;With the design agreed, the implementation covers FMPE and NPSE. The builder takes the architecture as &lt;code&gt;model&lt;/code&gt;, one of &lt;code&gt;mlp&lt;/code&gt;, &lt;code&gt;ada_mlp&lt;/code&gt;, &lt;code&gt;transformer&lt;/code&gt; or &lt;code&gt;transformer_cross_attn&lt;/code&gt;, plus &lt;code&gt;estimator_type&lt;/code&gt; for flow versus score and &lt;code&gt;sde_type&lt;/code&gt; for the noise schedule.&lt;/p&gt;

&lt;p&gt;One detail I liked: this family has fields that only make sense for one side. &lt;code&gt;sigma_min&lt;/code&gt;, &lt;code&gt;sigma_max&lt;/code&gt; and the schedule settings belong to score estimators, and &lt;code&gt;gaussian_baseline&lt;/code&gt; belongs to flow matching. So the builder raises if you set a score field with &lt;code&gt;estimator_type="flow"&lt;/code&gt;, and the other way round.&lt;/p&gt;

&lt;p&gt;Interestingly the raw &lt;code&gt;build_vector_field_estimator&lt;/code&gt; function does &lt;em&gt;not&lt;/em&gt; do that. It silently ignores &lt;code&gt;sde_type&lt;/code&gt; if you asked for flow matching. So the builder is stricter than the function it wraps, which is the whole point of this project in one sentence.&lt;/p&gt;

&lt;p&gt;I also kept the applicability lists deliberately small and added tests that check them against the existing config classes, so the two cannot drift apart without CI noticing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rebasing lesson
&lt;/h2&gt;

&lt;p&gt;Now for the part that actually cost me time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1921" rel="noopener noreferrer"&gt;PR #1921&lt;/a&gt; was branched off &lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;PR #1920&lt;/a&gt;, which was branched off earlier work. As &lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;PR #1920&lt;/a&gt; changed during review, I kept merging &lt;code&gt;main&lt;/code&gt; back into my branch to stay current. What I ended up with was a branch history full of merge commits and small repair commits, and when it came time to present a clean PR, replaying that history with &lt;code&gt;git rebase&lt;/code&gt; was painful. Every conflict I had already resolved once came back to be resolved again.&lt;/p&gt;

&lt;p&gt;The thing that finally worked, and what Jan uses too, is to stop trying to replay history at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reset &lt;span class="nt"&gt;--soft&lt;/span&gt; &amp;lt;clean base&amp;gt;
git commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--soft&lt;/code&gt; keeps your working tree exactly as it is and just moves the branch pointer, so you get the final state of your code sitting on a clean base, and then you commit it in whatever shape makes sense for a reviewer. All the intermediate mess disappears because you never replay it.&lt;/p&gt;

&lt;p&gt;The other half of the lesson is that sbi squash merges its PRs. I had assumed that once &lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;PR #1920&lt;/a&gt; merged, its commits would stop showing up in &lt;a href="https://github.com/sbi-dev/sbi/pull/1921" rel="noopener noreferrer"&gt;PR #1921&lt;/a&gt;'s diff on their own. They do not. Squash merging creates a brand new commit on &lt;code&gt;main&lt;/code&gt;, so my branch's merge base never moves and those commits keep appearing until I explicitly rebase onto the new &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase &lt;span class="nt"&gt;--onto&lt;/span&gt; upstream/main &amp;lt;old base&amp;gt; &amp;lt;my branch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I now understand a lot better why maintainers care so much about branch hygiene. A reviewer opening a PR with 40 commits, half of which belong to a different PR, is going to have a bad time no matter how good the actual code is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also decided
&lt;/h2&gt;

&lt;p&gt;Two smaller things from the week 9 sync. Keyword harmonization is moving to the documentation PR rather than the vector field PR, so the VF PR stays focused and we do not break tutorials in the same change. And the work order for the rest of the project is now: vector field PR, then test refactoring, then documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1921" rel="noopener noreferrer"&gt;PR #1921&lt;/a&gt; is in review. After that comes the marginal estimator builder, and then the documentation PR. There is also a growing pile of test files, one per PR at this point, that we plan to consolidate once everything has merged.&lt;/p&gt;

&lt;p&gt;Thanks for following along, and see you in the next one!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>google</category>
      <category>ai</category>
      <category>gsoc</category>
    </item>
    <item>
      <title>Passing GSoC Midterms</title>
      <dc:creator>Satwik Sai Prakash Sahoo</dc:creator>
      <pubDate>Wed, 19 Aug 2026 12:41:19 +0000</pubDate>
      <link>https://dev.to/satwiksps/passing-gsoc-midterms-24kf</link>
      <guid>https://dev.to/satwiksps/passing-gsoc-midterms-24kf</guid>
      <description>&lt;p&gt;Hey everyone! Weeks 7 and 8 are done, and the biggest news first: I passed the midterm evaluation🎉. Half of GSoC is behind me now.&lt;/p&gt;

&lt;p&gt;These two weeks were less about writing new classes and more about going back and making the ones I already had a lot stricter. Here is what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;PR #1920:&lt;/a&gt; The &lt;code&gt;RatioEstimatorBuilder&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This PR adds the builder for the NRE family, so &lt;code&gt;NRE_A&lt;/code&gt;, &lt;code&gt;NRE_B&lt;/code&gt;, &lt;code&gt;NRE_C&lt;/code&gt; and &lt;code&gt;BNRE&lt;/code&gt; all get the same typed interface that NPE and NLE already had.&lt;/p&gt;

&lt;p&gt;The builder itself was honestly the easy part. By now the pattern is well established, so it was mostly mirroring what already worked, with &lt;code&gt;linear&lt;/code&gt;, &lt;code&gt;mlp&lt;/code&gt; and &lt;code&gt;resnet&lt;/code&gt; as the classifier options. The interesting part was what my mentor &lt;a href="https://github.com/janfb" rel="noopener noreferrer"&gt;Jan Teusen&lt;/a&gt; suggested we bundle into the same PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some improvements to the base class
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;PR #1920&lt;/a&gt; was the first PR that proved the shared base class serves a &lt;em&gt;third&lt;/em&gt; family. My Mentor, &lt;a href="https://github.com/janfb" rel="noopener noreferrer"&gt;Jan&lt;/a&gt; pointed out that this was exactly the right moment to fix the validation gaps in that base, because then the NRE builder and the vector field builder that was coming next would inherit the fixes for free, instead of me retrofitting four builders later.&lt;/p&gt;

&lt;p&gt;So we folded a hardening bundle into the same PR:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invalid &lt;code&gt;Literal&lt;/code&gt; values now raise at construction.&lt;/strong&gt; This was the real gap. If you typed a wrong &lt;em&gt;field name&lt;/em&gt;, Python already raised a &lt;code&gt;TypeError&lt;/code&gt; for you. But if you typed a wrong &lt;em&gt;value&lt;/em&gt; on a correctly named field, like &lt;code&gt;z_score_input="idependent"&lt;/code&gt;, nothing happened until you called &lt;code&gt;.train()&lt;/code&gt; and it blew up much later. Now it fails immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model-incompatible kwargs now raise too.&lt;/strong&gt; Passing &lt;code&gt;num_blocks&lt;/code&gt; to a &lt;code&gt;linear&lt;/code&gt; classifier used to be silently dropped, and the same argument on &lt;code&gt;mlp&lt;/code&gt; crashed late. Both are caught at construction now by inspecting the target build function's signature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;frozen=True&lt;/code&gt; on the config dataclasses.&lt;/strong&gt; Configs are now immutable. If you want a different setting, you make a new object instead of mutating the old one. This sounds like a small thing but it removes a whole class of "&lt;em&gt;I changed the config after passing it to the trainer, why did nothing happen&lt;/em&gt;" confusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A custom &lt;code&gt;__repr__&lt;/code&gt;.&lt;/strong&gt; Printing a config now shows only the fields that matter for the chosen model, instead of a wall of thirty &lt;code&gt;None&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uniform public API exposure.&lt;/strong&gt; The mixed density builder was not exported at the same level as the others, so I fixed that too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The z-score rename
&lt;/h2&gt;

&lt;p&gt;The other thing that landed in this PR was a rename I had been putting off. The builders used &lt;code&gt;z_score_x&lt;/code&gt; and &lt;code&gt;z_score_y&lt;/code&gt;, inherited from the low level &lt;code&gt;build_*&lt;/code&gt; functions. The problem is that those names are ambiguous once you have more than one family. For NPE, &lt;code&gt;z_score_x&lt;/code&gt; actually standardizes theta, not x, because NPE models theta given x. So the name says one thing and does another depending on which trainer you are in.&lt;/p&gt;

&lt;p&gt;We renamed them to &lt;code&gt;z_score_input&lt;/code&gt; and &lt;code&gt;z_score_condition&lt;/code&gt;. &lt;code&gt;input&lt;/code&gt; is whatever the model is modelling and &lt;code&gt;condition&lt;/code&gt; is whatever it is conditioning on, and the trainer decides which is which. The downstream &lt;code&gt;build_*&lt;/code&gt; functions keep their old names, and the builder translates at the boundary.&lt;/p&gt;

&lt;p&gt;The reason we did it now and not later is simple: the builder API has never shipped in a release yet, so this rename is free. After a release it becomes a breaking change. It was in three builders at that point and &lt;a href="https://github.com/sbi-dev/sbi/pull/1921" rel="noopener noreferrer"&gt;PR #1921&lt;/a&gt; was about to add a fourth.&lt;/p&gt;

&lt;h2&gt;
  
  
  A shift in how we work
&lt;/h2&gt;

&lt;p&gt;The other outcome of the midterm sync was a change in workflow for the second half of the project. Instead of Jan handing me a design and me implementing it, I now write a short design proposal first, as a markdown file in our GSoC repo, and Jan reviews it &lt;em&gt;before&lt;/em&gt; I write any code.&lt;/p&gt;

&lt;p&gt;The first one of these is for the vector field estimators, which is the next family. I will be honest, writing a design document before touching the editor feels slower at first. But the point is that a design mistake caught in a markdown file costs an hour, and the same mistake caught in review costs a week of rework. We also decided to start the documentation earlier than originally planned, instead of leaving it all to the very end.&lt;/p&gt;

&lt;h2&gt;
  
  
  An honest note about week 8
&lt;/h2&gt;

&lt;p&gt;Week 8 was a slow week for me. I had other commitments and reduced my workload, so &lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;PR #1920&lt;/a&gt; did not move much and the vector field design proposal did not get written. I flagged it in the sync rather than quietly falling behind, and Jan was completely fine about it. Back to normal pace from now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/sbi-dev/sbi/pull/1920" rel="noopener noreferrer"&gt;PR #1920&lt;/a&gt; needs the hardening review addressed and then it can merge. After that I write the vector field design proposal, get it reviewed, and only then start on &lt;code&gt;VectorFieldEstimatorBuilder&lt;/code&gt; for FMPE and NPSE.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and see you in the next update!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gsoc</category>
      <category>github</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Halfway Through GSoC</title>
      <dc:creator>Satwik Sai Prakash Sahoo</dc:creator>
      <pubDate>Mon, 20 Jul 2026 05:33:56 +0000</pubDate>
      <link>https://dev.to/satwiksps/halfway-through-gsoc-14ba</link>
      <guid>https://dev.to/satwiksps/halfway-through-gsoc-14ba</guid>
      <description>&lt;p&gt;Hey everyone! We have officially hit the halfway mark of the Google Summer of Code (GSoC) 2026 coding period. &lt;/p&gt;

&lt;p&gt;These past two weeks have been heavily focused on expanding the &lt;a href="https://summerofcode.withgoogle.com/programs/2026/projects/P5QOhl9F" rel="noopener noreferrer"&gt;Neural Network Builder API&lt;/a&gt; beyond just standard NPE (Neural Posterior Estimation) models. I have successfully merged PRs &lt;a href="https://github.com/sbi-dev/sbi/pull/1904" rel="noopener noreferrer"&gt;#1904&lt;/a&gt; and &lt;a href="https://github.com/sbi-dev/sbi/pull/1912" rel="noopener noreferrer"&gt;#1912&lt;/a&gt; into the &lt;code&gt;gsoc-2026&lt;/code&gt; branch for &lt;strong&gt;&lt;a href="https://github.com/sbi-dev/sbi" rel="noopener noreferrer"&gt;sbi&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is a breakdown of what I worked on, the architectural challenges we solved, and some vital lessons learned about failing fast and role-neutral design.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sbi-dev/sbi/pull/1904" rel="noopener noreferrer"&gt;PR #1904&lt;/a&gt;: Integrating the Builder into NLE
&lt;/h2&gt;

&lt;p&gt;In my previous PR, we successfully wired the &lt;code&gt;DensityEstimatorBuilder&lt;/code&gt; into NPE trainers. The next logical step was to do the same for Neural Likelihood Estimation (NLE) trainers. However, this brought up an immediate architectural challenge regarding how the builder signs its inputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Role-Neutral &lt;code&gt;build()&lt;/code&gt; Signature
&lt;/h3&gt;

&lt;p&gt;Previously, the &lt;code&gt;build()&lt;/code&gt; method took &lt;code&gt;(batch_theta, batch_x)&lt;/code&gt;. This made sense for NPE, which estimates the posterior distribution 

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;span class="mord"&gt;∣&lt;/span&gt;&lt;span class="mord"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, meaning 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is the input and 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is the condition.&lt;/p&gt;

&lt;p&gt;However, NLE estimates the likelihood 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;p&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mord"&gt;∣&lt;/span&gt;&lt;span class="mord"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
. In this case, 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is the input and 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;θ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is the condition.&lt;/p&gt;

&lt;p&gt;If we kept the &lt;code&gt;(batch_theta, batch_x)&lt;/code&gt; signature on the base builder, it would be extremely confusing and semantically incorrect for NLE.&lt;/p&gt;

&lt;p&gt;The fix? &lt;strong&gt;Role-Neutral Signatures&lt;/strong&gt;.&lt;br&gt;
My mentor &lt;a href="https://github.com/janfb" rel="noopener noreferrer"&gt;Jan Teusen&lt;/a&gt; suggested that I should refactor the base builder and &lt;code&gt;DensityEstimatorBuilder&lt;/code&gt; methods to accept &lt;code&gt;(batch_input, batch_condition)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, the trainers handle the role assignment via the &lt;code&gt;_wrap_builder()&lt;/code&gt; closure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For NPE:&lt;/strong&gt; &lt;code&gt;build(batch_input=batch_theta, batch_condition=batch_x)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For NLE:&lt;/strong&gt; &lt;code&gt;build(batch_input=batch_x, batch_condition=batch_theta)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This change cleanly decouples the mathematical model the network represents from the data types the network consumes.&lt;/p&gt;
&lt;h3&gt;
  
  
  Parameterizing Tests
&lt;/h3&gt;

&lt;p&gt;During review, my mentor also suggested that the NPE and NLE integration tests were essentially "twin" tests doing the exact same thing with different objects, we parameterized them!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;TRAINERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;NPE_C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;posterior_nn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;theta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NLE_A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;likelihood_nn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trainer,factory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TRAINERS&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_string_emits_deprecation_warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prior&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MultivariateNormal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;eye&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;FutureWarning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deprecated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;show_progress_bars&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This saved a ton of code duplication while keeping the coverage identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sbi-dev/sbi/pull/1912" rel="noopener noreferrer"&gt;PR #1912&lt;/a&gt;: Mixed Density Estimators (MNLE &amp;amp; MNPE)
&lt;/h2&gt;

&lt;p&gt;For datasets containing mixed data types (both continuous and discrete variables), sbi uses Mixed Neural Posterior/Likelihood Estimators (MNPE/MNLE).&lt;/p&gt;

&lt;p&gt;To support these, I created a sibling class to our main builder: the &lt;code&gt;MixedDensityEstimatorBuilder&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  "continuous_model" over "model"
&lt;/h3&gt;

&lt;p&gt;Our main design choice here was using the field &lt;code&gt;continuous_model&lt;/code&gt; instead of just &lt;code&gt;model&lt;/code&gt;. This is because the supported mixed architectures include non-flow models like MDN and MADE. Now, a user can explicitly configure the continuous sub-network: &lt;code&gt;MixedDensityEstimatorBuilder(continuous_model="nsf")&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failing Fast is Better Than Failing Late
&lt;/h3&gt;

&lt;p&gt;MNPE requires a &lt;code&gt;MixedDensityEstimator&lt;/code&gt;. If a user accidentally passed a standard &lt;code&gt;DensityEstimatorBuilder&lt;/code&gt; (e.g., &lt;code&gt;MNPE(density_estimator=DensityEstimatorBuilder())&lt;/code&gt;), the code would gladly accept it, train an entire continuous normal flow for hundreds of epochs, and then crash at the very end when it finally checked the returned class type.&lt;/p&gt;

&lt;p&gt;Failing at the end of a long training run with a cryptic error is terrible UX.&lt;/p&gt;

&lt;p&gt;We added a strict type-check inside the trainer initialization to fail fast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_EstimatorBuilderBase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MixedDensityEstimatorBuilder&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MNPE requires a MixedDensityEstimatorBuilder; got &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Use &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MixedDensityEstimatorBuilder(continuous_model=...).&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if a user makes this mistake, the code crashes instantly with clear instructions on how to fix it, saving hours of compute time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Catching Configuration Drift
&lt;/h3&gt;

&lt;p&gt;We defined &lt;code&gt;_VALID_MIXED_CONTINUOUS_MODELS&lt;/code&gt; to validate inputs. However, hardcoding this set means it could drift away from the actual supported models in the factory if another developer adds a model later. To prevent this, I added a test specifically designed to catch configuration drift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_valid_continuous_models_match_builders&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sbi.neural_nets.net_builders.mixed_nets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;model_builders&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;_VALID_MIXED_CONTINUOUS_MODELS&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;frozenset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_builders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;We are halfway there! The continuous and mixed density estimators are successfully wired into the system. Next up, we will be tackling the remaining likelihood and classifier builders to complete the NN Builder ecosystem.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and stay tuned for the next update!&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>opensource</category>
      <category>google</category>
      <category>github</category>
    </item>
    <item>
      <title>First internal integration of the new API</title>
      <dc:creator>Satwik Sai Prakash Sahoo</dc:creator>
      <pubDate>Mon, 29 Jun 2026 17:47:42 +0000</pubDate>
      <link>https://dev.to/satwiksps/first-internal-integration-of-the-new-api-25ii</link>
      <guid>https://dev.to/satwiksps/first-internal-integration-of-the-new-api-25ii</guid>
      <description>&lt;p&gt;Hello again! The GSoC coding period is in full swing, and weeks 3 and 4 have been absolutely packed with progress. &lt;/p&gt;

&lt;p&gt;Following up on the foundational work from my first PR, I have just successfully merged two major PRs (&lt;a href="https://github.com/sbi-dev/sbi/pull/1877" rel="noopener noreferrer"&gt;#1877&lt;/a&gt; and &lt;a href="https://github.com/sbi-dev/sbi/pull/1882" rel="noopener noreferrer"&gt;#1882&lt;/a&gt;) into the gsoc-2026 branch for &lt;a href="https://github.com/sbi-dev" rel="noopener noreferrer"&gt;sbi&lt;/a&gt;. These PRs introduce the first concrete builder class and wire it directly into the core Neural Posterior Estimation (NPE) trainers. &lt;/p&gt;

&lt;p&gt;Here is a dive into what I built, the technical hurdles, and a very valuable lesson I learned about software architecture along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sbi-dev/sbi/pull/1877" rel="noopener noreferrer"&gt;PR #1877&lt;/a&gt;: The &lt;code&gt;DensityEstimatorBuilder&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The primary goal of this phase was to replace the old, opaque &lt;code&gt;posterior_nn()&lt;/code&gt; and &lt;code&gt;likelihood_nn()&lt;/code&gt; factory closures with something typed, inspectable, and much more robust. &lt;/p&gt;

&lt;p&gt;To solve this, I introduced the &lt;code&gt;DensityEstimatorBuilder&lt;/code&gt;. It inherits from the base contract we established in Week 1 and serves as the unified entry point for creating neural networks in &lt;code&gt;sbi&lt;/code&gt;. Using the &lt;code&gt;__post_init__&lt;/code&gt; method in Python dataclasses, it immediately validates the model name against a &lt;code&gt;_VALID_DENSITY_MODELS&lt;/code&gt; set, failing early if the user provides an unknown architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Major architectural shift
&lt;/h3&gt;

&lt;p&gt;Initially, our plan dictated that the &lt;code&gt;build()&lt;/code&gt; method should take a &lt;code&gt;BuildContext&lt;/code&gt; object. The idea was that this context would hold all necessary information, including pre-computed z-scoring stats, and pass it neatly down the chain. &lt;/p&gt;

&lt;p&gt;However, as I implemented the body of the &lt;code&gt;build()&lt;/code&gt; method, my mentor &lt;a href="https://github.com/janfb" rel="noopener noreferrer"&gt;Jan Teusen&lt;/a&gt; noticed that the &lt;code&gt;context&lt;/code&gt; parameter wasn't actually being used. Every piece of information the builder needed could be derived directly from the raw &lt;code&gt;batch_theta&lt;/code&gt; and &lt;code&gt;batch_x&lt;/code&gt; tensors. &lt;/p&gt;

&lt;p&gt;We realized that forcing the &lt;code&gt;BuildContext&lt;/code&gt; into this signature was a &lt;strong&gt;premature abstraction&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Instead of holding onto a design just because it was the original plan, we decided to defer the context object entirely until the z-scoring stats are actually pre-computed in a later phase. We updated our roadmap and simplified the &lt;code&gt;build&lt;/code&gt; signature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_theta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch_x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was a great decision to defer the &lt;code&gt;context&lt;/code&gt; implementation to work on the breadth first and then move towards depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/sbi-dev/sbi/pull/1882" rel="noopener noreferrer"&gt;PR #1882&lt;/a&gt;: Integrating the Builder into NPE Trainers
&lt;/h2&gt;

&lt;p&gt;With the builder merged, the next step was integration. I updated &lt;code&gt;PosteriorEstimatorTrainer&lt;/code&gt;, &lt;code&gt;NPE_B&lt;/code&gt;, &lt;code&gt;NPE_C&lt;/code&gt;, and &lt;code&gt;MNPE&lt;/code&gt; to accept the new &lt;code&gt;DensityEstimatorBuilder&lt;/code&gt; instead of relying solely on strings or callables.&lt;/p&gt;

&lt;p&gt;To maintain backward compatibility while moving the API forward, a graceful deprecation path was implemented. If a user passes a string (e.g., &lt;code&gt;"maf"&lt;/code&gt;), the code still works perfectly, but it now emits a &lt;code&gt;FutureWarning&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;density_estimator&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_build_neural_net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_wrap_builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DensityEstimatorBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Passing a string for `density_estimator` is deprecated. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Use DensityEstimatorBuilder(model=...) instead.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;FutureWarning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;stacklevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_build_neural_net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;posterior_nn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_EstimatorBuilderBase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_build_neural_net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_wrap_builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_build_neural_net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;density_estimator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Overal feedback on my work
&lt;/h2&gt;

&lt;p&gt;The code review for &lt;a href="https://github.com/sbi-dev/sbi/pull/1882" rel="noopener noreferrer"&gt;PR #1882&lt;/a&gt; was intense but incredibly rewarding. My mentor provided feedback on how to write tests that are not just concise, but strong and explicit in their intent.&lt;/p&gt;

&lt;p&gt;For example, I originally wrote a test that checked if passing a callable avoided triggering the deprecation warning. But I wasn't actually asserting that no warning was thrown, I was just running the code and assuming silence meant success.&lt;/p&gt;

&lt;p&gt;My mentor showed me how to use &lt;code&gt;warnings.catch_warnings()&lt;/code&gt; with a strict filter to instantly fail the test if a &lt;code&gt;FutureWarning&lt;/code&gt; leaked through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;warnings&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;catch_warnings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;simplefilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;FutureWarning&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NPE_C&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;density_estimator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;show_progress_bars&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also did a deep dive into correct type hinting and managing default arguments. I initially left &lt;code&gt;density_estimator="maf"&lt;/code&gt; as the default argument in the &lt;code&gt;NPE_C&lt;/code&gt; initialization. My mentor pointed out that this would cause the deprecation warning to fire every single time a user initialized the class without arguments! The fix was to change the type hint default to &lt;code&gt;None&lt;/code&gt; and handle the &lt;code&gt;"maf"&lt;/code&gt; fallback inside the logic block.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Weeks 3 and 4 were a massive leap forward for the API refactor. We now have a working, integrated builder that correctly handles all continuous density estimators.&lt;/p&gt;

&lt;p&gt;Next up, I will be tackling the remaining likelihood and classifier builders. Thanks for following along on this journey, and see you in the next update!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>gsoc</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>Merging My First PR for GSoC!</title>
      <dc:creator>Satwik Sai Prakash Sahoo</dc:creator>
      <pubDate>Mon, 15 Jun 2026 13:12:48 +0000</pubDate>
      <link>https://dev.to/satwiksps/merging-my-first-pr-for-gsoc-542h</link>
      <guid>https://dev.to/satwiksps/merging-my-first-pr-for-gsoc-542h</guid>
      <description>&lt;p&gt;Hey everyone! The official coding period for Google Summer of Code (GSoC) has begun, and I am thrilled to share that my very first Pull Request (PR) for the &lt;strong&gt;sbi&lt;/strong&gt; (Simulation-Based Inference) repository has been officially merged! &lt;/p&gt;

&lt;p&gt;These first two weeks have been packed with writing code, writing tests, and most importantly, going through an incredibly insightful code review process with my mentors. &lt;/p&gt;

&lt;p&gt;Here is a breakdown of what I worked on, the technical challenges I faced, and the best practices I learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Laying the Foundation (&lt;a href="https://github.com/sbi-dev/sbi/pull/1872" rel="noopener noreferrer"&gt;PR #1872&lt;/a&gt;)
&lt;/h2&gt;

&lt;p&gt;My GSoC project is focused on refactoring the Neural Network (NN) Builder API. Before we can build the new, shiny network builders, we need a solid foundation. This first PR was all about setting up the necessary data structures and renaming existing protocols to make room for the new architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The New &lt;code&gt;build_context.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I created a new file to house the core pieces needed to set up a neural network. This centralizes how the data is prepared before it hits the network:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ZScoreConfig&lt;/code&gt;&lt;/strong&gt;: Tracks how the user wants to preprocess data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ZScoreStats&lt;/code&gt;&lt;/strong&gt;: Holds the calculated mean and standard deviation for the data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BuildContext&lt;/code&gt;&lt;/strong&gt;: Bundles everything required to build a network (shapes, device, dtype, and z-score stats) into a single, clean dataclass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;compute_z_score_stats()&lt;/code&gt;&lt;/strong&gt;: A helper function to calculate these statistics directly from the training tensors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Clearing the Naming Space
&lt;/h3&gt;

&lt;p&gt;To make the codebase more intuitive, I had to rename a few core components across 16 different trainer files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changed &lt;code&gt;ConditionalEstimatorBuilder&lt;/code&gt; to &lt;code&gt;ConditionalEstimatorBuildFn&lt;/code&gt;. This clarifies that the protocol is actually a function, not an object, and frees up the "Builder" name for upcoming classes.&lt;/li&gt;
&lt;li&gt;Changed &lt;code&gt;_EstimatorConfigBase&lt;/code&gt; to &lt;code&gt;_EstimatorBuilderBase&lt;/code&gt;, giving it an empty &lt;code&gt;build()&lt;/code&gt; method to prepare for the next phase.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Big Takeaways from Code Review
&lt;/h2&gt;

&lt;p&gt;Getting the code working was only half the battle. The review process with my mentor, taught me several advanced Python and PyTorch practices. &lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Tensor Devices
&lt;/h3&gt;

&lt;p&gt;When creating the &lt;code&gt;BuildContext&lt;/code&gt;, the data (&lt;code&gt;x&lt;/code&gt; and &lt;code&gt;theta&lt;/code&gt;) are passed in as PyTorch tensors. My mentor pointed out that if &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;theta&lt;/code&gt; accidentally end up on different devices (e.g., one on CPU, one on GPU), it will cause runtime failures. I updated the code to explicitly check that both tensors share the same device and added GPU-specific pytest runs to ensure everything handles device placement gracefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dataclasses and Tensor Equality
&lt;/h3&gt;

&lt;p&gt;I originally set up my statistics classes as &lt;code&gt;@dataclass(frozen=True)&lt;/code&gt;. However, because they hold PyTorch tensors, this creates a subtle bug. PyTorch's implementation of equality (&lt;code&gt;==&lt;/code&gt;) for tensors returns a boolean tensor, not a single boolean value, which breaks the dataclass equality checks. &lt;/p&gt;

&lt;p&gt;The fix was simple but crucial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ZScoreStats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;theta_mean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;theta_std&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;x_mean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;x_std&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding &lt;code&gt;eq=False&lt;/code&gt;, equality becomes identity-based, completely bypassing the PyTorch tensor comparison crash.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graceful Deprecation with PEP 562
&lt;/h3&gt;

&lt;p&gt;Since &lt;code&gt;ConditionalEstimatorBuilder&lt;/code&gt; was used by the community, we couldn't just delete the name and break everyone's code. Instead of leaving a dummy class, I learned how to use module-level &lt;code&gt;__getattr__&lt;/code&gt; to intercept the import and throw a &lt;code&gt;FutureWarning&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__getattr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ConditionalEstimatorBuilder&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;warnings&lt;/span&gt;
        &lt;span class="n"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;`ConditionalEstimatorBuilder` has been renamed to &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;`ConditionalEstimatorBuildFn`. The old name still works but will be &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;removed in a future release. Update your import to: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;`from sbi.neural_nets.estimators.base import ConditionalEstimatorBuildFn`.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;FutureWarning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;stacklevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ConditionalEstimatorBuildFn&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;module &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt; has no attribute &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Refactoring Tests
&lt;/h3&gt;

&lt;p&gt;I used Claude Opus to help generate initial test coverage for the new context types. It gave me class-based tests. However, the &lt;code&gt;sbi&lt;/code&gt; standard relies heavily on function-based tests and &lt;code&gt;pytest&lt;/code&gt; fixtures. I completely refactored the test suite to use &lt;code&gt;@pytest.mark.parametrize&lt;/code&gt; to test valid combinations of inputs, ensuring maximum coverage with minimal, highly readable code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;With the foundation types successfully merged into the &lt;code&gt;gsoc-2026&lt;/code&gt; branch, the groundwork is officially laid out! Up next is &lt;a href="https://github.com/sbi-dev/sbi/pull/1877" rel="noopener noreferrer"&gt;PR #1877&lt;/a&gt;, where I will be adding the actual &lt;code&gt;DensityEstimatorBuilder&lt;/code&gt; with dynamic &lt;code&gt;build()&lt;/code&gt; dispatching.&lt;/p&gt;

&lt;p&gt;Stay tuned for week 3 and 4 updates!&lt;/p&gt;

</description>
      <category>gsoc</category>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>GSoC Community Bonding Period: Getting Ready to Code</title>
      <dc:creator>Satwik Sai Prakash Sahoo</dc:creator>
      <pubDate>Thu, 04 Jun 2026 06:51:17 +0000</pubDate>
      <link>https://dev.to/satwiksps/gsoc-community-bonding-period-getting-ready-to-code-5hl9</link>
      <guid>https://dev.to/satwiksps/gsoc-community-bonding-period-getting-ready-to-code-5hl9</guid>
      <description>&lt;p&gt;Hey everyone! Welcome back to my Google Summer of Code (GSoC) journey.&lt;/p&gt;

&lt;p&gt;In my last post, I shared the story of how I got into open source and was selected for GSoC with NumFOCUS to work on the &lt;strong&gt;&lt;a href="https://summerofcode.withgoogle.com/programs/2026/projects/P5QOhl9F" rel="noopener noreferrer"&gt;Neural Network Builder API Refactor&lt;/a&gt;&lt;/strong&gt; project for &lt;strong&gt;sbi&lt;/strong&gt; (Simulation-Based Inference).&lt;/p&gt;

&lt;p&gt;Since the official announcement, the past three weeks have been dedicated to the &lt;strong&gt;Community Bonding Period&lt;/strong&gt;. It is designed to help contributors get to know their mentors, understand the community culture, and familiarize themselves with the codebase and tools.&lt;/p&gt;

&lt;p&gt;Here is exactly what I did during these past three weeks to get ready for the main coding phase!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Kickoff Meeting
&lt;/h2&gt;

&lt;p&gt;We started the bonding period with a great kickoff call on Google Meet. It was a joint meeting that included the mentors for both of the selected sbi projects, the selected GSoC candidates. We were also joined by the mentee who successfully completed the GSoC project for sbi last year!&lt;/p&gt;

&lt;p&gt;Everyone introduced themselves, and it was incredibly inspiring to meet the team face-to-face (virtually!) and hear about everyone's backgrounds. Having a former GSoC student there was a huge bonus, as they shared some great insights into what to expect in the coming months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Machine
&lt;/h2&gt;

&lt;p&gt;A big part of getting started is making sure the development environment is properly configured. During our meetings, we discussed the machine setup in detail to ensure both candidates had everything required to run and test the sbi codebase locally without any hiccups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embracing AI Coding Assistants
&lt;/h2&gt;

&lt;p&gt;One of the most interesting discussions we had was about using AI coding assistants. In the modern development world, tools like these are becoming standard, and our mentors actually encouraged us to use them!&lt;/p&gt;

&lt;p&gt;However, they emphasized using them carefully and strictly following project guidelines. To help us get the most out of these tools without compromising code quality, the mentors shared some excellent Claude code tutorials and provided us with resources to level up our AI coding skills. It is all about using AI to augment our capabilities, not to blindly write code for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diving into the Codebase and Planning
&lt;/h2&gt;

&lt;p&gt;With the setup out of the way, the rest of the three weeks was all about reading. I spent a lot of time diving deep into the sbi codebase and reading through the documentation properly.&lt;/p&gt;

&lt;p&gt;Understanding the architecture is critical for my API refactor project. To keep everything organized and on track, we were provided with a &lt;code&gt;plan.md&lt;/code&gt; file. This document serves as our roadmap, outlining exactly what needs to be tackled, the skills required, and the milestones we need to hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;The community bonding period was incredibly helpful. It gave me the time to get comfortable with the team, set up my workflow, and build a solid foundation before the heavy lifting begins.&lt;/p&gt;

&lt;p&gt;Now, the official coding phase is here! I am beyond excited to start pushing commits and working on the Neural Network Builder API Refactor.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and stay tuned. I will be sharing more technical updates and lessons learned as I start writing code for GSoC!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>gsoc</category>
      <category>machinelearning</category>
      <category>python</category>
    </item>
    <item>
      <title>How I Got Selected for GSoC: My Journey with sbi</title>
      <dc:creator>Satwik Sai Prakash Sahoo</dc:creator>
      <pubDate>Sun, 31 May 2026 19:55:27 +0000</pubDate>
      <link>https://dev.to/satwiksps/how-i-got-selected-for-gsoc-my-journey-with-sbi-56g5</link>
      <guid>https://dev.to/satwiksps/how-i-got-selected-for-gsoc-my-journey-with-sbi-56g5</guid>
      <description>&lt;p&gt;Hey everyone! I am super excited to share that I have been selected for &lt;strong&gt;Google Summer of Code (GSoC)&lt;/strong&gt; with &lt;strong&gt;NumFOCUS&lt;/strong&gt;. I will be working on the &lt;strong&gt;&lt;a href="https://summerofcode.withgoogle.com/programs/2026/projects/P5QOhl9F" rel="noopener noreferrer"&gt;Neural Network Builder API Refactor&lt;/a&gt;&lt;/strong&gt; project for the organization &lt;strong&gt;sbi (Simulation-Based Inference)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you are wondering how to get started with open source or what the GSoC process is really like, here is the story of how I got here. Stay tuned, because I will keep updating you on my GSoC journey and sharing my experiences as I get deeper into the project!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Started Open Source
&lt;/h2&gt;

&lt;p&gt;Being a fan of Linus Torvalds, I was always fascinated by open source. Also, I wanted to test my coding skills. I wanted to know: &lt;em&gt;Is my code actually good enough for the real world?&lt;/em&gt; In my first year of college, I spent a lot of time doing competitive programming. At the same time, I was teaching myself machine learning and mathematics, working mostly with Python and PyTorch. &lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the Right Project
&lt;/h2&gt;

&lt;p&gt;When I decided to try open source, I wanted to find an organization that actually matched what I was learning. Because I was already into machine learning and PyTorch, I went looking for something in that space.&lt;/p&gt;

&lt;p&gt;That is how I found &lt;strong&gt;sbi&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Making my first Pull Request (PR) was a little scary, but the maintainers at sbi were incredibly welcoming. They patiently guided me through my early, beginner-level PRs. Thanks to their feedback, I learned so much about how to write clean, high-quality code. &lt;/p&gt;

&lt;h2&gt;
  
  
  The GSoC Surprise
&lt;/h2&gt;

&lt;p&gt;After a few months of fixing bugs and contributing, I found out from the maintainers that sbi would be participating in GSoC this year. They encouraged me and appreciated the work I had done over the past few months, for which I will be forever grateful.&lt;/p&gt;

&lt;p&gt;But there was one problem: I hadn't researched GSoC at all. I had no idea what the requirements were or how to write a project proposal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Help with the Proposal
&lt;/h2&gt;

&lt;p&gt;Writing the proposal was tough since it was my first time. Luckily, I didn't have to do it alone. &lt;/p&gt;

&lt;p&gt;The maintainers also stepped up to mentor me. They reviewed my proposal drafts, gave me detailed feedback, and helped me improve it step by step until it was ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;All that hard work and community support paid off, and I got accepted! &lt;/p&gt;

&lt;p&gt;The GSoC community bonding period just finished up. In my next blog post, I will share exactly what I did during this bonding period to get ready for the main coding phase. &lt;/p&gt;

&lt;p&gt;Thanks for reading, and stay tuned!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>google</category>
      <category>gsoc</category>
    </item>
  </channel>
</rss>
