<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-us">
    <generator uri="https://gohugo.io/" version="0.158.0">Hugo</generator>
    <title>Numpy</title>
    <subtitle>Posts from the Scientific Python community</subtitle>
    <id>https://blog.scientific-python.org/tags/numpy/</id>
    <updated>2026-09-29T00:00:00Z</updated>
    <author>
        <name>Blog</name>
    </author>
    <link rel="self" href="https://blog.scientific-python.org/tags/numpy/atom.xml" type="application/atom" />
    <link rel="alternate" href="https://blog.scientific-python.org/tags/numpy/" type="text/html" />
    <entry>
      <title>Making np.searchsorted up to 25× Faster in NumPy 2.5</title>
      <id>https://blog.scientific-python.org/numpy/searchsorted/</id>
      <updated>2026-09-29T00:00:00Z</updated>
      <published>2026-09-29T00:00:00Z</published>
      <link rel="alternate" href="https://blog.scientific-python.org/numpy/searchsorted/?utm_source=atom_feed" type="text/html" />
      <content type="html" xml:base="https://blog.scientific-python.org/numpy/searchsorted/">&lt;blockquote&gt;How vectorizing independent binary searches and reducing per-query state can make np.searchsorted up to 25× faster.&lt;/blockquote&gt;&lt;p&gt;&lt;code&gt;np.searchsorted&lt;/code&gt; is NumPy&amp;rsquo;s implementation of the binary search algorithm. This is one of the fundamental search algorithms and is used in the Python scientific ecosystem for functionality such as histogram computation and interval lookups. Any optimization benefits libraries such as SciPy and scikit-learn, as well as the broader Python scientific ecosystem.&lt;/p&gt;
&lt;p&gt;Several case studies, such as &lt;a href=&#34;https://curiouscoding.nl/posts/binsearch/&#34;&gt;Binary search variants and the effects of batching&lt;/a&gt; and &lt;a href=&#34;https://en.algorithmica.org/hpc/data-structures/binary-search/&#34;&gt;Algorithmica&amp;rsquo;s Binary Search case study&lt;/a&gt; explore techniques such as branch elimination, batching, and cache-friendly data layouts to binary search performance. In this post, we explore how those ideas can be expressed using NumPy&amp;rsquo;s vectorized primitives.&lt;/p&gt;
&lt;p&gt;We will derive a vectorized formulation that outperforms NumPy 2.4&amp;rsquo;s searchsorted implementation, and then port the resulting algorithm back into NumPy. The change was included in NumPy 2.5, achieving up to a 25× speedup in our benchmarks.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;searchsorted&lt;/code&gt; is also part of the &lt;a href=&#34;https://data-apis.org/array-api/latest/API_specification/generated/array_api.searchsorted.html#array_api.searchsorted&#34;&gt;Python Array API Standard&lt;/a&gt;. This allows us to compare how different array libraries implement the same operation and exploit parallelism.&lt;/p&gt;
&lt;h2 id=&#34;problem-definition&#34;&gt;Problem definition&lt;a class=&#34;headerlink&#34; href=&#34;#problem-definition&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Given a static sorted array and a sequence of query keys, find the insertion position of each key in the array.&lt;/p&gt;
&lt;p&gt;A classic pure-Python implementation runs one binary search per key:&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;searchsorted_py&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;res&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;empty&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dtype&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;x&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;enumerate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;x&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;res&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img src=&#34;/numpy/searchsorted/images/figure1-fs8.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Running time per query grows logarithmically as the input size grows (note the logarithmic scale of x-axis).&lt;/p&gt;
&lt;p&gt;For benchmarking, we generated two random arrays of uniformly distributed &lt;code&gt;np.int32&lt;/code&gt; integers. The keys (i.e. the elements being searched) had a fixed length of 10,000, while the length of the sorted array varied up to $2^{30}$ ($4\ GiB$). Both keys and values arrays are contiguous in memory. Each benchmark was repeated 50 times, and we report the minimum execution time.&lt;/p&gt;
&lt;p&gt;The benchmarks were run on a MacBook Pro with an Apple M1 Pro and 32 GB of memory. The M1 Pro has 128 KB of L1 data cache per performance core, enough to hold $2^{15}$ 32-bit integers, and a 12 MB L2 cache shared by its performance cores, enough to hold $1.5 * 2^{21}$ 32-bit integers.&lt;/p&gt;
&lt;h2 id=&#34;batching-with-numpy-arrays&#34;&gt;Batching with NumPy arrays&lt;a class=&#34;headerlink&#34; href=&#34;#batching-with-numpy-arrays&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The baseline implementation performs one binary search per query. Each search is independent, but executed sequentially in Python. We can adapt the algorithm so multiple binary searches make progress together in batches. For that we can represent the state of all searches as arrays and update them simultaneously using vectorized operations.&lt;/p&gt;
&lt;p&gt;In NumPy, operations on arrays are executed in compiled C&#43;&#43; loops. This removes Python overhead and allows the CPU to efficiently process large batches of independent work.&lt;/p&gt;
&lt;h2 id=&#34;lets-vectorize-our-binary-search&#34;&gt;Let&amp;rsquo;s vectorize our binary search&lt;a class=&#34;headerlink&#34; href=&#34;#lets-vectorize-our-binary-search&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To vectorize the algorithm, we reinterpret scalar variables as array state. Instead of a single &lt;code&gt;lo&lt;/code&gt; and &lt;code&gt;hi&lt;/code&gt;, we maintain one value per query. In our original implementation, the state consists of &lt;code&gt;lo&lt;/code&gt;, &lt;code&gt;hi&lt;/code&gt;, and &lt;code&gt;res&lt;/code&gt;. Since &lt;code&gt;lo&lt;/code&gt; ends up containing the final result, we can focus on tracking just &lt;code&gt;lo&lt;/code&gt; and &lt;code&gt;hi&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This first vectorized implementation is a direct translation of the previous algorithm.&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;searchsorted_py_np&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;zeros&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;shape&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dtype&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;full&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;shape&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dtype&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;True&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# True for each element position where lo_i &amp;lt; hi_i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;active&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;not&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;active&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;c1&#34;&gt;# this is basically `while lo &amp;lt; hi:` in the pure-Python version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# only index those entries where active is true, so we don&amp;#39;t modify already computed positions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;mid_a&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;active&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;xs_a&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;active&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;mask&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mid_a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;xs_a&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;active&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mask&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid_a&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;active&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;active&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mask&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;active&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid_a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img src=&#34;/numpy/searchsorted/images/figure2-fs8.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;In this implementation, different queries can shrink their search intervals at different rates, so they may require different numbers of iterations to converge.&lt;/p&gt;
&lt;p&gt;For example, consider searching for the keys &lt;code&gt;[-1, 2]&lt;/code&gt; in the array &lt;code&gt;[0, 1]&lt;/code&gt;. For the query &lt;code&gt;2&lt;/code&gt;, the first iteration computes &lt;code&gt;mid = 1&lt;/code&gt; and sets &lt;code&gt;lo = mid &#43; 1 = 2&lt;/code&gt;, so the interval becomes &lt;code&gt;[2, 2)&lt;/code&gt; and the search converges in one step. For the query &lt;code&gt;-1&lt;/code&gt;, the update instead sets &lt;code&gt;hi = mid = 1&lt;/code&gt;, leaving the interval &lt;code&gt;[0, 1)&lt;/code&gt; after the first step. This requires an additional iteration to collapse the interval to &lt;code&gt;[0, 0)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Because the searches can converge at different times, we need to keep track of which queries are still active. The active mask identifies the queries whose search intervals have not yet converged, allowing us to update only those queries.&lt;/p&gt;
&lt;h3 id=&#34;making-all-searches-take-the-same-number-of-steps&#34;&gt;Making all searches take the same number of steps&lt;a class=&#34;headerlink&#34; href=&#34;#making-all-searches-take-the-same-number-of-steps&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The important observation is that binary search does not actually need to terminate independently for each key. We can tweak each iteration update in a way that, once a search has converged, subsequent iterations can leave its interval unchanged.&lt;/p&gt;
&lt;p&gt;We maintain the invariant that the insertion position lies in &lt;code&gt;[lo, hi)&lt;/code&gt;. At each iteration, every interval is reduced to roughly half its previous size. After &lt;code&gt;np.ceil(np.log2(n))&lt;/code&gt; iterations, every interval has collapsed to a single position.&lt;/p&gt;
&lt;p&gt;For simplicity, this implementation assumes &lt;code&gt;a&lt;/code&gt; is non-empty.&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;searchsorted_py_np_fixed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;n&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;zeros&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;shape&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dtype&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;full&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;shape&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dtype&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;int32&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ceil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;log2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;go_left&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;xs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# for each position i:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# if go_left_i is True, we keep the `lo_i` value and `hi_i` is updated to `mid_i`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;# if go_left_i is False, we keep the `hi_i` value and `lo_i` is updated to `mid_i`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;go_left&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;go_left&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;hi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Removing the &lt;code&gt;active&lt;/code&gt; tracker makes it up to 2× faster.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/numpy/searchsorted/images/figure3-fs8.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;A similar formulation can already be found in the Python ecosystem. For example, &lt;a href=&#34;https://github.com/jax-ml/jax/blob/a6e4a8b95a731269bdf23e5b3e30da2f8494bb28/jax/_src/numpy/hijax.py#L330&#34;&gt;JAX’s scan-based implementation&lt;/a&gt;&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;body_fun&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;low&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;high&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;state&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;low&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;high&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;low&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;//&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# use this form to avoid overflow&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;go_left&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;op&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;sorted_arr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;lax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;go_left&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;low&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lax&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;go_left&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;high&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;n_levels&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ceil&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;log2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;n&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 id=&#34;can-numpy-beat-numpy&#34;&gt;Can NumPy beat NumPy?&lt;a class=&#34;headerlink&#34; href=&#34;#can-numpy-beat-numpy&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let’s compare the performance of this vectorized implementation with NumPy’s native &lt;code&gt;searchsorted&lt;/code&gt; (using NumPy 2.4).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/numpy/searchsorted/images/figure4-fs8.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Our vectorized Python implementation can be an order of magnitude faster than the native one for inputs with several keys. To understand why, let&amp;rsquo;s take a look at &lt;code&gt;NumPy 2.4&lt;/code&gt; implementation:&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Tag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;side_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;side&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;binsearch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ret_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;PyArrayObject&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;using&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;typename&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Tag&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cmp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;side_to_cmp&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Tag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;side&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;min_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;max_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;last_key_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key_len&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;last_key_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_len&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_len&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ret&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ret_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;cm&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;         * Updating only one of the indices based on the previous key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;         * gives the search a big boost when keys are sorted, but slightly
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;         * slows down things for purely random ones.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;         */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cmp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;last_key_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;max_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;min_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;max_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;last_key_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;min_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;max_idx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;min_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;max_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;min_idx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;cmp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mid_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;n&#34;&gt;min_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;                &lt;span class=&#34;n&#34;&gt;max_idx&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid_idx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ret&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;min_idx&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Ignoring the pointer arithmetic details, the core algorithm is a classic binary search executed independently for each key. There is also an optimization that reuses previous search bounds when the input keys are sorted.&lt;/p&gt;
&lt;p&gt;This implementation performs one binary search per key, where each search is a fully sequential process. Each iteration of the binary search depends on the result of the previous one (the midpoint determines which part of the array is inspected next). This creates a dependency chain within each search: the next memory access depends on the result of the previous comparison.&lt;/p&gt;
&lt;p&gt;For large arrays, binary-search reads also tend to be cache-unfriendly, since each step may require a read from a different cache line. Cache misses have a greater impact on the sequential implementation because each step may stall waiting for the previous memory access to complete.&lt;/p&gt;
&lt;p&gt;The vectorized implementation performs the same logical step across all queries at once (all queries advance at each step together). &lt;strong&gt;With multiple independent searches, the CPU can have several memory accesses in flight at once.&lt;/strong&gt; This aligns with the observed running time once the array size exceeds the L1 and L2 cache sizes.&lt;/p&gt;
&lt;h3 id=&#34;can-we-optimize-numpy&#34;&gt;Can we optimize NumPy?&lt;a class=&#34;headerlink&#34; href=&#34;#can-we-optimize-numpy&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The previous vectorized implementation maintains two arrays, &lt;code&gt;lo&lt;/code&gt; and &lt;code&gt;hi&lt;/code&gt;, to represent the search interval for each query. If we were to port this exact implementation into NumPy natively, it would require using $O(K)$ additional memory where &lt;code&gt;K&lt;/code&gt; is the number of queries. Even though this approach is potentially faster, this is unacceptable for memory-sensitive workloads.&lt;/p&gt;
&lt;p&gt;To reduce the state required, we can reformulate binary search in terms of interval boundaries. Instead of tracking both &lt;code&gt;lo&lt;/code&gt; and &lt;code&gt;hi&lt;/code&gt; for each query, we describe each interval using its left boundary and its length.&lt;/p&gt;
&lt;p&gt;The key observation is that if we structure the algorithm so that all queries shrink their intervals by the same amount at each iteration, then every interval has the same length at each iteration. This means we do not need to store a separate &lt;code&gt;hi&lt;/code&gt; per query: it can be reconstructed from a single array &lt;code&gt;lo&lt;/code&gt; and a global length. Note that this still needs $O(K)$ space for the output, but it requires only $O(1)$ memory beyond that output.&lt;/p&gt;
&lt;p&gt;This gives us the following Python implementation:&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;searchsorted_py_np_fast_where&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;K&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;shape&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;shape&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;zeros&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;K&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;dtype&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;intp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Invariant: the insertion index lies in [base, base &#43; length]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Final step: result is either base and base &#43; 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;keys&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;base&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Only a single array &lt;code&gt;base&lt;/code&gt; is needed to store the per-query state, while length is shared across all queries. The output is written directly into base, so no additional state array is required. This implementation still requires allocating a temporary &lt;code&gt;mid&lt;/code&gt; to hold the midpoints, but this can be avoided in the C&#43;&#43; port.&lt;/p&gt;
&lt;p&gt;This formulation is closely related to the branchless binary search approach discussed in &lt;a href=&#34;https://en.algorithmica.org/hpc/data-structures/binary-search/#removing-branches&#34;&gt;Algorithmica&amp;rsquo;s case study&lt;/a&gt;. In the formulation we use, the invariant range is &lt;code&gt;[base, base &#43; length]&lt;/code&gt;. Therefore a final step is required when &lt;code&gt;length = 1&lt;/code&gt; to resolve whether the insertion point falls to the left or right of &lt;code&gt;base&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The reformulated implementation is significantly faster:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/numpy/searchsorted/images/figure6-fs8.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;porting-it-into-c&#34;&gt;Porting it into C&#43;&#43;&lt;a class=&#34;headerlink&#34; href=&#34;#porting-it-into-c&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The performance results show the benefit of reducing the per-query state. We can now translate it almost directly to C&#43;&#43; with $O(1)$ additional memory.&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Tag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;side_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;side&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;static&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;binsearch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ret&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ret_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;PyArrayObject&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;using&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;typename&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Tag&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;auto&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cmp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;side_to_cmp&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Tag&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;side&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// If the array length is 0 we return all 0s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arr_len&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&#43;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ret&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ret_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cm&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;    base = np.zeros(K, dtype=np.intp)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;    We unroll the first iteration for the following reasons:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;        1. ret is not initialized with the bases, so we save |keys| writes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;        by not having to initialize it with 0s.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;        2. By assuming the initial base for every key is 0, we also save
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;        |keys| reads.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;        3. In the first iteration, all elements are compared against the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;        median. So we can store it in a variable and use it for all keys.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;    This initial block replaces the initialization loop that is used for the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;    arr_len==0 case. Note that when arr_len = 1, then half is 0 so the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;    following block initializes the array as with 0s.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;    */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;interval_length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;interval_length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;interval_length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// length -&amp;gt; ceil(length / 2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&#43;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ret&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ret_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cmp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mid_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cm&#34;&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;        while length &amp;gt; 1:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;            half = length &amp;gt;&amp;gt; 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;            length -= half
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;            mid = base &#43; half
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;            base = np.where(keys &amp;gt; arr[mid], mid, base)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;    */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;interval_length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;interval_length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;interval_length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&#43;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ret&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ret_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mid_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cmp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mid_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;half&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// base = np.where(keys &amp;gt; arr[base], base &#43; 1, base)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&#43;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;npy_intp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ret&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ret_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;key&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;i&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;cmp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;T&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arr&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&#43;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;arr_str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;key_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note that we exploited a property of the first iteration of the binary search. Because the initial value of every result entry is implicitly zero, we can skip writing and reading those values during the first iteration. Moreover, in the first iteration all elements are compared against the same median, so we can read its value once instead of &lt;code&gt;K&lt;/code&gt; times.&lt;/p&gt;
&lt;p&gt;This implementation was ported directly into NumPy as part of PR &lt;a href=&#34;https://github.com/numpy/numpy/pull/30517&#34;&gt;#30517&lt;/a&gt;, which was included in the &lt;a href=&#34;https://numpy.org/devdocs/release/2.5.0-notes.html#improved-performance-of-numpy-searchsorted&#34;&gt;2.5 release&lt;/a&gt;. Now let&amp;rsquo;s do a final comparison between NumPy 2.4 and 2.5, and our vectorized Python implementation:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/numpy/searchsorted/images/figure7-fs8.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The native 2.5 version is up to 25× faster than NumPy 2.4&amp;rsquo;s implementation in our benchmarks. Compared with the vectorized Python implementation, the C&#43;&#43; implementation can be up to 2× as fast for smaller arrays. This difference becomes less significant as the array size grows. There is also a memory advantage over the vectorized Python implementation: the Python implementation requires additional arrays to store the search state (&lt;code&gt;low&lt;/code&gt; and &lt;code&gt;mid&lt;/code&gt;, or &lt;code&gt;base&lt;/code&gt; and &lt;code&gt;base &#43; length&lt;/code&gt;), whereas the C&#43;&#43; implementation keeps length as a scalar. As a result, the C&#43;&#43; implementation uses only $O(1)$ additional memory, while the NumPy formulation requires memory proportional to the number of queries.&lt;/p&gt;
&lt;h3 id=&#34;ecosystem-comparison&#34;&gt;Ecosystem Comparison&lt;a class=&#34;headerlink&#34; href=&#34;#ecosystem-comparison&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We can compare our optimized NumPy 2.5 against other libraries in the ecosystem. For this experiment, we selected the Python libraries JAX, TensorFlow, and PyTorch.&lt;/p&gt;
&lt;p&gt;TensorFlow and PyTorch follow a different approach from JAX and NumPy. While JAX and NumPy leverage vectorized/batched operations to hide memory latency, TensorFlow and PyTorch parallelize independent searches across CPU threads. Search keys are partitioned into batches that are processed by different threads. For more details, see the &lt;a href=&#34;https://github.com/pytorch/pytorch/blob/b1bb860d3c812371b89a9725407230216e7369b5/aten/src/ATen/native/Bucketization.cpp#L88&#34;&gt;PyTorch&lt;/a&gt; and &lt;a href=&#34;https://github.com/tensorflow/tensorflow/blob/bb8d3f2443d70ec8c2aae1288fbf5782c771aa60/tensorflow/core/kernels/searchsorted_op.cc#L67&#34;&gt;TensorFlow&lt;/a&gt; implementations.&lt;/p&gt;
&lt;p&gt;In the benchmarks, we limited parallelism to 8 cores and we increased the number of query keys from 10,000 to 20,000. This gives the multithreaded implementations enough independent work to amortize thread-scheduling overhead.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/numpy/searchsorted/images/figure8-fs8.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The benchmark shows that NumPy is competitive with the selected libraries in our benchmarks. All implementations exhibit similar behavior once the search array grows beyond the CPU cache.&lt;/p&gt;
&lt;p&gt;If we disable multithreading, the performance of PyTorch and TensorFlow degrades, and both exhibit a similar trend to NumPy 2.4&amp;rsquo;s implementation. Once the search array grows beyond the CPU cache, the cost of memory accesses dominates.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/numpy/searchsorted/images/figure9-fs8.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;It would be worth benchmarking whether both techniques could be combined: batching binary searches within each thread. However, once the memory subsystem becomes saturated, additional cores can compete for the same memory bandwidth. At that point, improving the memory access patterns may be a more promising direction, for example by using a different layout such as the Eytzinger layout (discussed in detail in the &lt;a href=&#34;https://en.algorithmica.org/hpc/data-structures/binary-search/#eytzinger-layout&#34;&gt;Algorithmica book&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;a class=&#34;headerlink&#34; href=&#34;#conclusion&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We made &lt;code&gt;np.searchsorted&lt;/code&gt; up to 25x faster in our benchmarks. Given NumPy&amp;rsquo;s reach in the Python ecosystem, this optimization will benefit several libraries that depend on it. Other libraries in the Python ecosystem with their own binary search implementation may also benefit from adopting similar batching techniques.&lt;/p&gt;
&lt;p&gt;Interestingly, we used NumPy array primitives to derive an initial Python implementation that outperformed NumPy 2.4&amp;rsquo;s implementation. This shows how powerful NumPy&amp;rsquo;s array primitives can be for implementing highly performant algorithms. &lt;strong&gt;A vectorized NumPy implementation in Python can outperform a scalar native implementation by exploiting independent work&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Cache-friendly layouts such as the Eytzinger layout are another interesting direction for making &lt;code&gt;searchsorted&lt;/code&gt; faster. It would be interesting to explore whether the array API could expose such layouts through an interface like &lt;code&gt;searchsorted(arr, keys, layout=&amp;quot;eytzinger&amp;quot;)&lt;/code&gt;, although this would require carefully defining the API semantics since the Eytzinger representation is not sorted.&lt;/p&gt;
</content>
      <category term="Numpy" scheme="https://blog.scientific-python.org/tags/" label="Numpy" />
      <category term="Performance" scheme="https://blog.scientific-python.org/tags/" label="Performance" />
      <category term="Binary-Search" scheme="https://blog.scientific-python.org/tags/" label="Binary-Search" />
      <category term="Searchsorted" scheme="https://blog.scientific-python.org/tags/" label="Searchsorted" />
    </entry>
    <entry>
      <title>A Year of Typing: My NumPy Fellowship Retrospective</title>
      <id>https://blog.scientific-python.org/numpy/fellowship-program-2025-retrospective/</id>
      <updated>2026-01-08T00:00:00Z</updated>
      <published>2026-01-08T00:00:00Z</published>
      <link rel="alternate" href="https://blog.scientific-python.org/numpy/fellowship-program-2025-retrospective/?utm_source=atom_feed" type="text/html" />
      <content type="html" xml:base="https://blog.scientific-python.org/numpy/fellowship-program-2025-retrospective/">&lt;blockquote&gt;A Year of Typing: My NumPy Fellowship Retrospective&lt;/blockquote&gt;&lt;p&gt;It’s been exactly one year since I started my journey as a NumPy Fellow, and looking back, it has honestly been the best job I&amp;rsquo;ve ever had. My main goal for 2025 was to push the boundaries of static typing within the Scientific Python ecosystem. I&amp;rsquo;m happy to report that we didn&amp;rsquo;t just push the boundaries; we reshaped them.&lt;/p&gt;
&lt;p&gt;Here is a high-level look at what we achieved, from making &lt;code&gt;numpy&lt;/code&gt; fully type-checked to bridging the gap between scientific computing and the wider Python typing community.&lt;/p&gt;
&lt;h2 id=&#34;numpy-is-now-fully-type-checked&#34;&gt;NumPy is Now Fully Type-Checked&lt;a class=&#34;headerlink&#34; href=&#34;#numpy-is-now-fully-type-checked&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the biggest wins this year is that NumPy is now fully type-checked. When I started, there were significant gaps between the runtime behavior and the typing stubs.&lt;/p&gt;
&lt;p&gt;For example, I spent a lot of time integrating &lt;code&gt;stubtest&lt;/code&gt; — a &lt;a href=&#34;https://github.com/python/mypy&#34;&gt;mypy&lt;/a&gt; tool that checks if stubs match the runtime — into the CI pipeline. For this I had to fix thousands (yes, thousands) of errors in the stubs. Now typing correctness is enforced by running &lt;code&gt;stubtest&lt;/code&gt; and &lt;code&gt;mypy&lt;/code&gt; on the stubs in CI, ensuring that technical debt doesn&amp;rsquo;t creep back in.&lt;/p&gt;
&lt;p&gt;Crucially, NumPy is now largely compatible with the official &lt;a href=&#34;https://typing.python.org/en/latest/&#34;&gt;Python typing specification&lt;/a&gt;. I helped drop support for Python 3.11 to update our stubs to use the modern PEP 695 syntax, making the codebase cleaner and more future-proof.&lt;/p&gt;
&lt;p&gt;Besides static typing, since NumPy 2.4.0, all function- and class-signatures can now also be inspected at runtime using &lt;code&gt;inspect.signature&lt;/code&gt;. This is a game-changer for runtime type-checkers like &lt;a href=&#34;https://github.com/beartype/beartype&#34;&gt;beartype&lt;/a&gt; and &lt;a href=&#34;https://github.com/agronholm/typeguard&#34;&gt;typeguard&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Over the past year I&amp;rsquo;ve made &lt;a href=&#34;https://github.com/numpy/numpy/pulls?q=is%3Apr&amp;#43;author%3Ajorenham&amp;#43;created%3A2025-01-01..2025-12-31&amp;#43;is%3Amerged&#34;&gt;hundreds of contributions&lt;/a&gt; to NumPy, so there&amp;rsquo;s a good chance I&amp;rsquo;m forgetting some important achievements.&lt;/p&gt;
&lt;h2 id=&#34;the-shape-typing-frontier-numtype&#34;&gt;The Shape-Typing Frontier: NumType&lt;a class=&#34;headerlink&#34; href=&#34;#the-shape-typing-frontier-numtype&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A massive part of my fellowship was dedicated to the &amp;ldquo;holy grail&amp;rdquo; of array typing: shape-typing. For this I had to rely on type-checker behavior that isn&amp;rsquo;t well-specified, and therefore subject to change. Using these typing acrobatics in NumPy would be too risky, so we decided to create a new project for this, called &lt;a href=&#34;https://github.com/numpy/numtype&#34;&gt;NumType&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When NumType is installed, your static type-checker will use its &lt;code&gt;.pyi&lt;/code&gt; stubs instead of those bundled with NumPy. There are three main advantages to this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Improved ufunc annotations.&lt;/li&gt;
&lt;li&gt;Full support of the &lt;a href=&#34;https://numpy.org/neps/nep-0050-scalar-promotion.html&#34;&gt;NEP 50&lt;/a&gt; promotion rules for all scalars, exhaustively verified to be 100% accurate.&lt;/li&gt;
&lt;li&gt;Experimental shape-typing with automatic static broadcasting types.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &amp;ldquo;magic&amp;rdquo; types that enable the dtype promotion and shape-type broadcasting are currently only accessible from the private type-check-only &lt;code&gt;_numtype&lt;/code&gt; API. But the plan is to eventually make these part of the public &lt;code&gt;numtype&lt;/code&gt; API.&lt;/p&gt;
&lt;p&gt;But before you drop everything to install NumType, note that it&amp;rsquo;s currently in alpha, so there&amp;rsquo;s no backwards-compatibility guarantee. However, if you &lt;em&gt;do&lt;/em&gt; decide to use it and encounter an issue, be sure to complain about it in high definition at &lt;a href=&#34;https://github.com/numpy/numtype/issues&#34;&gt;https://github.com/numpy/numtype/issues&lt;/a&gt; :)&lt;/p&gt;
&lt;h2 id=&#34;strengthening-the-ecosystem-scipy-stubs-and-beyond&#34;&gt;Strengthening the Ecosystem: &lt;code&gt;scipy-stubs&lt;/code&gt; and Beyond&lt;a class=&#34;headerlink&#34; href=&#34;#strengthening-the-ecosystem-scipy-stubs-and-beyond&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Typing NumPy is useless if the libraries built &lt;em&gt;on top&lt;/em&gt; of it aren&amp;rsquo;t typed. A significant portion of my time went into &lt;code&gt;scipy-stubs&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We transferred ownership of &lt;a href=&#34;https://github.com/scipy/scipy-stubs/&#34;&gt;&lt;code&gt;scipy-stubs&lt;/code&gt;&lt;/a&gt; — which started as &lt;code&gt;jorenham/scipy-stubs&lt;/code&gt; — to the official SciPy organization.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scipy-stubs&lt;/code&gt; now covers the full SciPy API, and only uses &lt;code&gt;Any&lt;/code&gt; when absolutely necessary.&lt;/li&gt;
&lt;li&gt;It has grown massively — it now contains over 72,000 lines of code (according to &lt;a href=&#34;https://github.com/boyter/scc&#34;&gt;&lt;code&gt;scc&lt;/code&gt;&lt;/a&gt;), making it the largest hand-written stubs-only Python package; even if you include &lt;code&gt;typeshed&lt;/code&gt;&amp;rsquo;s standard library stubs (which currently counts 69,439 lines of code).&lt;/li&gt;
&lt;li&gt;We added runtime support for the generic types, including the sparse arrays, probability distributions, and interpolation classes.&lt;/li&gt;
&lt;li&gt;I helped large libraries such as &lt;a href=&#34;https://github.com/pandas-dev/pandas&#34;&gt;&lt;code&gt;pandas&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/jax-ml/jax&#34;&gt;&lt;code&gt;jax&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/colour-science/colour&#34;&gt;&lt;code&gt;colour&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://github.com/apache/spark&#34;&gt;&lt;code&gt;pyspark&lt;/code&gt;&lt;/a&gt; adopt &lt;code&gt;scipy-stubs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also made sure to spread the love to other corners of the ecosystem by adding typing support to &lt;a href=&#34;https://github.com/numpy/numpy-financial&#34;&gt;&lt;code&gt;numpy-financial&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/wolph/numpy-stl&#34;&gt;&lt;code&gt;numpy-stl&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://pypi.org/project/numpy-quaddtype/&#34;&gt;&lt;code&gt;numpy-quaddtype&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/pydata/numexpr&#34;&gt;&lt;code&gt;numexpr&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://github.com/joblib/threadpoolctl&#34;&gt;&lt;code&gt;threadpoolctl&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;bridging-communities&#34;&gt;Bridging Communities&lt;a class=&#34;headerlink&#34; href=&#34;#bridging-communities&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Perhaps the achievement I&amp;rsquo;m most proud of is the collaboration with the type-checker maintainers. Scientific Python has complex needs that often stretch the limits of Python&amp;rsquo;s type system.&lt;/p&gt;
&lt;p&gt;Throughout the year, I discovered and investigated bugs in all five major type-checkers: &lt;a href=&#34;https://github.com/python/mypy&#34;&gt;&lt;code&gt;mypy&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/microsoft/pyright&#34;&gt;&lt;code&gt;pyright&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/DetachHead/basedpyright&#34;&gt;&lt;code&gt;basedpyright&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/facebook/pyrefly&#34;&gt;&lt;code&gt;pyrefly&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://github.com/astral-sh/ty&#34;&gt;&lt;code&gt;ty&lt;/code&gt;&lt;/a&gt;. &lt;a href=&#34;https://github.com/python/mypy/pulls?q=is%3Apr&amp;#43;author%3Ajorenham&amp;#43;created%3A2025-01-01..2025-12-31&#34;&gt;Some&lt;/a&gt; of the &lt;code&gt;mypy&lt;/code&gt; bugs I even managed to fix myself. We fixed critical bugs affecting NumPy users and improved analysis times.&lt;/p&gt;
&lt;p&gt;I feel that this work has brought the Scientific Python community much closer to the Python Typing community. I&amp;rsquo;m incredibly grateful to the maintainers of these tools for their responsiveness and willingness to collaborate.&lt;/p&gt;
&lt;h2 id=&#34;wrapping-up&#34;&gt;Wrapping Up&lt;a class=&#34;headerlink&#34; href=&#34;#wrapping-up&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This fellowship has been an absolute privilege, and I feel like I&amp;rsquo;ve made the most out of it. If you want to dive into the nitty-gritty details, you can find all of my activity on my GitHub profile (&lt;a href=&#34;https://github.com/jorenham&#34;&gt;&lt;code&gt;@jorenham&lt;/code&gt;&lt;/a&gt;), but for now, I&amp;rsquo;m just happy to have made those squiggly lines a bit more meaningful.&lt;/p&gt;
&lt;p&gt;Thanks to everyone who made this possible, and type safe!&lt;/p&gt;
</content>
      <category term="Numpy" scheme="https://blog.scientific-python.org/tags/" label="Numpy" />
      <category term="Developer-in-Residence" scheme="https://blog.scientific-python.org/tags/" label="Developer-in-Residence" />
    </entry>
    <entry>
      <title>NumPy&#39;s Second Developer in Residence: Joren Hammudoglu</title>
      <id>https://blog.scientific-python.org/numpy/fellowship-program-2025/</id>
      <updated>2025-01-01T00:00:00Z</updated>
      <published>2025-01-01T00:00:00Z</published>
      <link rel="alternate" href="https://blog.scientific-python.org/numpy/fellowship-program-2025/?utm_source=atom_feed" type="text/html" />
      <content type="html" xml:base="https://blog.scientific-python.org/numpy/fellowship-program-2025/">&lt;blockquote&gt;Introducing NumPy&amp;rsquo;s second developer in residence, Joren Hammudoglu.&lt;/blockquote&gt;&lt;p&gt;The NumPy team is excited to announce the appointment of Joren Hammudoglu (@jorenham) as the second NumPy Developer in Residence. For the second time, the project is in a position to use its project funds to pay for a full year of maintainer time through the NumPy Fellowship Program.&lt;/p&gt;
&lt;p&gt;Joren has been the driving force behind the improvements in NumPy&amp;rsquo;s support for static typing since he started contributing in mid-2024. He has authored a lot of the improvements — from the annotations themselves to CI support and working towards fundamental design improvements like ndarray shape typing — and helps guide and integrate the work of other NumPy contributors in this area, and engages with upstream projects like MyPy and Pyright and the typing standards/PEP process to help move static typing support for the ecosystem as a whole forward. He also contributes widely to static typing support in the ecosystem, as the author of &lt;code&gt;scipy-stubs&lt;/code&gt;, &lt;code&gt;numtype&lt;/code&gt; and more.&lt;/p&gt;
&lt;p&gt;The NumPy Steering Council sees Joren’s appointment to this role as both recognition of his contributions and expertise as well as an opportunity to continue improving NumPy’s static typing support — an area that few maintainers are knowledgeable about but a significant fraction of end users tends to care about a lot.&lt;/p&gt;
&lt;p&gt;Joren&amp;rsquo;s role is for the calendar year 2025. Thanks to individual and corporate donations, as well as payments from Tidelift, NumPy has been able to fund another full-time position for one year, following the first such role held by Sayed Adel in 2023. The funds are still &lt;a href=&#34;https://numpy.org/neps/nep-0048-spending-project-funds.html&#34;&gt;transparently administered&lt;/a&gt; on Open Collective.&lt;/p&gt;
&lt;p&gt;Welcome aboard, Joren! We&amp;rsquo;re excited to see the impact of your work.&lt;/p&gt;
</content>
      <category term="Numpy" scheme="https://blog.scientific-python.org/tags/" label="Numpy" />
      <category term="Developer-in-Residence" scheme="https://blog.scientific-python.org/tags/" label="Developer-in-Residence" />
    </entry>
    <entry>
      <title>NumPy 2.0: an evolutionary milestone</title>
      <id>https://blog.scientific-python.org/numpy/numpy2/</id>
      <updated>2024-06-17T00:00:00Z</updated>
      <published>2024-06-17T00:00:00Z</published>
      <link rel="alternate" href="https://blog.scientific-python.org/numpy/numpy2/?utm_source=atom_feed" type="text/html" />
      <content type="html" xml:base="https://blog.scientific-python.org/numpy/numpy2/">&lt;blockquote&gt;We announce the release of NumPy 2.0, which brings major improvements in functionality and usability. Improvements to NumPy internals set the stage for future development. We discuss the motivation behind breaking changes and how those impact users, as well as some of the history of the 2.0 development process.&lt;/blockquote&gt;&lt;p&gt;Eighteen years since the release of NumPy 1.0, we are thrilled to announce the
launch of NumPy 2.0! This major release marks a significant milestone in the
evolution of NumPy, bringing a wealth of enhancements and improvements to users,
and setting the stage for future feature development.&lt;/p&gt;
&lt;p&gt;NumPy has improved and evolved over the past 18 years, with many old releases bringing
significant performance, usability, and consistency improvements.
That said, our approach for a long time has been to make only incremental changes while
carefully managing backward compatibility. This approach minimizes user breakage,
but also limits the scope of improvements that can be made, both to the API and its underlying implementation.
Therefore, for this one-off major release, we are breaking backward
compatibility to implement significant improvements in NumPy&amp;rsquo;s type system. The
type system is fundamental to NumPy, and major behavioral changes could not be
made incrementally without mixing two different
type systems, which would be a recipe for disaster.&lt;/p&gt;
&lt;p&gt;The journey to an actual 2.0 release has been long, and it was difficult to
build the necessary momentum. In part, this may be because, for a time, the
NumPy developers associated a NumPy 2.0 release with nothing less than a
revolutionary rewrite of significant key pieces of the code base. Many of these
rewrites and changes happened over the years, but because of backward
compatibility concerns they remained largely invisible to the users. NumPy 2.0
is the culmination of these efforts, allowing us to discard some legacy
ABI (Application Binary Interface) that prevented future improvements.&lt;/p&gt;
&lt;p&gt;Some major changes to NumPy internals—required for key features in
2.0—have been in the works since 2019 at least.
We started concrete plans for the 2.0 release more than a year ago, at a four hour
long &lt;a href=&#34;https://github.com/numpy/archive/tree/main/2.0_developer_meeting&#34;&gt;public planning meeting&lt;/a&gt;
in April 2023. Many of the key changes were proposed and discussed. The key goals
we decided on there were perhaps even larger and more ambitious in scope than
some of us expected. This also unlocked some extra energy - which has been great to see.
After the meeting and over the course of the last year, NumPy enhancement
proposals (&lt;a href=&#34;https://numpy.org/neps/&#34;&gt;NEPs&lt;/a&gt;) were written,
reviewed, and implemented for each major change.&lt;/p&gt;
&lt;p&gt;Some key highlights are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Cleaned-up and streamlined Python API (&lt;a href=&#34;https://numpy.org/neps/nep-0052-python-api-cleanup.html&#34;&gt;NEP 52&lt;/a&gt;):
The Python API has undergone a thorough cleanup, making it easier to learn
and use NumPy. The main namespace has been reduced by approximately 10%, and
the more niche &lt;code&gt;numpy.lib&lt;/code&gt; namespace has been reduced by about 80%, providing
a clearer distinction between public and private API elements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Improved scalar promotion rules: The scalar promotion rules have been
updated, as proposed in &lt;a href=&#34;https://numpy.org/neps/nep-0050-scalar-promotion.html&#34;&gt;NEP 50&lt;/a&gt;
addressing surprising behaviors in type promotion, e.g. with zero dimensional arrays.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Powerful new DType API and a new string dtype: NumPy 2.0 introduces a new API
for implementing user-defined custom data types as proposed by
&lt;a href=&#34;https://numpy.org/neps/nep-0041-improved-dtype-support.html&#34;&gt;NEP 41&lt;/a&gt;. We used
this new API to implement &lt;code&gt;StringDType&lt;/code&gt;, offering efficient and painless
support for variable length strings which was proposed in
&lt;a href=&#34;https://numpy.org/neps/nep-0055-string_dtype.html&#34;&gt;NEP 55&lt;/a&gt;. And it is our hope
that enable future new data types with interesting new capabilities in the
PyData ecosystem and in NumPy itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Windows compatibility enhancements: The default 32-bit integer representation
on Windows has been updated to 64-bit on 64-bit architectures, addressing one
of the most common problems with having NumPy work portably across operating
systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Support for the Python array API standard: This is the first release to
include full support for the array API standard (v2022.12), made possible
by the new promotion rules, APIs, and API cleanup mentioned above.
We also aligned existing APIs and behavior with the standard,
as proposed in &lt;a href=&#34;https://numpy.org/neps/nep-0056-array-api-main-namespace.html&#34;&gt;NEP 56&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are just some of the more impactful changes in behavior and usability. In addition,
NumPy 2.0 contains significant performance and documentation improvements,
and much more - for an extensive list of changes, see
the &lt;a href=&#34;https://numpy.org/devdocs/release/2.0.0-notes.html&#34;&gt;NumPy 2 release notes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To adopt this major release, users will likely need to adjust existing code, but we
worked hard to strike a balance between improvements and ensuring that the
transition to NumPy 2.0 is as seamless as possible. We wrote a comprehensive
&lt;a href=&#34;https://numpy.org/devdocs/numpy_2_0_migration_guide.html&#34;&gt;migration guide&lt;/a&gt;,
and a &lt;a href=&#34;https://numpy.org/devdocs/numpy_2_0_migration_guide.html#ruff-plugin&#34;&gt;ruff plugin&lt;/a&gt;
that helps to update Python code so it will work with both NumPy 1.x and
NumPy 2.x.&lt;/p&gt;
&lt;p&gt;While we do require C API users to recompile their projects to support
NumPy 2.0, we prepared for this in NumPy 1.25 already. The build process was
simplified so that you can now compile with the latest NumPy version,
and remain backward compatible.
This means that projects build with NumPy 2.x are &amp;ldquo;magically&amp;rdquo; compatible with
1.x. It also means that projects no longer need to build their binaries using
the oldest supported version of NumPy.&lt;/p&gt;
&lt;p&gt;We knew throughout development that rolling out NumPy 2.0
would be (temporarily) disruptive, because of the backwards-incompatible API and
ABI changes. We spent an extraordinary amount of effort communicating these
changes, helping downstream projects adapt, tracking compatibility of popular
open source projects (see, e.g.,
&lt;a href=&#34;https://github.com/numpy/numpy/issues/26191&#34;&gt;numpy#26191&lt;/a&gt;), and completing the
release process at limited pace to provide time for adoption. No
doubt, the next few weeks will bring to light some new challenges, however we fully expect these
to be manageable and well worth it in the long run.&lt;/p&gt;
&lt;p&gt;The NumPy 2.0 release is the result of a collaborative, largely volunteer,
effort spanning many years and involving contributions from a diverse community
of developers. In addition, many of the changes above would not have been
possible without funders and institutional sponsors allowing several team members
to work on NumPy as part of their day jobs. We&amp;rsquo;d like to acknowledge in particular:
the Gordon and Betty Moore Foundation, the Alfred P. Sloan Foundation,
NASA, NVIDIA, Quansight Labs, the Chan Zuckerberg Initiative, and Tidelift.&lt;/p&gt;
&lt;p&gt;We are excited about future improvements to NumPy, many of which will be
possible due to changes in NumPy 2.0. See &lt;a href=&#34;https://numpy.org/neps/roadmap.html&#34;&gt;the NumPy roadmap&lt;/a&gt;
for some features in the pipeline or on the wishlist. Let&amp;rsquo;s
continue working together to improve NumPy and the scientific Python and PyData
ecosystem!&lt;/p&gt;</content>
      <category term="News" scheme="https://blog.scientific-python.org/tags/" label="News" />
      <category term="Numpy" scheme="https://blog.scientific-python.org/tags/" label="Numpy" />
    </entry>
    <entry>
      <title>Best Practices for Using NumPy&#39;s Random Number Generators</title>
      <id>https://blog.scientific-python.org/numpy/numpy-rng/</id>
      <updated>2024-01-26T23:22:46&#43;02:00</updated>
      <published>2024-01-26T23:22:46&#43;02:00</published>
      <link rel="alternate" href="https://blog.scientific-python.org/numpy/numpy-rng/?utm_source=atom_feed" type="text/html" />
      <content type="html" xml:base="https://blog.scientific-python.org/numpy/numpy-rng/">&lt;blockquote&gt;Best Practices for Using NumPy&amp;rsquo;s Random Number Generators&lt;/blockquote&gt;&lt;p&gt;Given the practical challenges of achieving true randomness, deterministic algorithms, known as Pseudo Random Number Generators (RNGs), are employed in science to create sequences that mimic randomness. These generators are used for simulations, experiments, and analysis where it is essential to have numbers that appear unpredictable. I want to share here what I have learned about best practices with pseudo RNGs and especially the ones available in &lt;a href=&#34;https://numpy.org/&#34;&gt;NumPy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A pseudo RNG works by updating an internal state through a deterministic algorithm. This internal state is initialized with a value known as a seed and each update produces a number that appears randomly generated. The key here is that the process is deterministic, meaning that if you start with the same seed and apply the same algorithm, you will get the same sequence of internal states (and numbers). Despite this determinism, the resulting numbers exhibit properties of randomness, appearing unpredictable and evenly distributed. Users can either specify the seed manually, providing a degree of control over the generated sequence, or they can opt to let the RNG object automatically derive the seed from system entropy. The latter approach enhances unpredictability by incorporating external factors into the seed.&lt;/p&gt;
&lt;p&gt;I assume a certain knowledge of NumPy and that NumPy 1.17 or greater is used. The reason for this is that great new features were introduced in the &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/index.html&#34;&gt;random&lt;/a&gt; module of version 1.17. As &lt;code&gt;numpy&lt;/code&gt; is usually imported as &lt;code&gt;np&lt;/code&gt;, I will sometimes use &lt;code&gt;np&lt;/code&gt; instead of &lt;code&gt;numpy&lt;/code&gt;. Finally, RNG will always mean pseudo RNG in the rest of this blog post.&lt;/p&gt;
&lt;h3 id=&#34;the-main-messages&#34;&gt;The main messages&lt;a class=&#34;headerlink&#34; href=&#34;#the-main-messages&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Avoid using the global NumPy RNG. This means that you should avoid using &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/generated/numpy.random.seed.html&#34;&gt;&lt;code&gt;np.random.seed&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;np.random.*&lt;/code&gt; functions, such as &lt;code&gt;np.random.random&lt;/code&gt;, to generate random values.&lt;/li&gt;
&lt;li&gt;Create a new RNG and pass it around using the &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng&#34;&gt;&lt;code&gt;np.random.default_rng&lt;/code&gt;&lt;/a&gt; function.&lt;/li&gt;
&lt;li&gt;Be careful with parallel random number generation and use the &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/parallel.html&#34;&gt;strategies provided by NumPy&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note that, with older versions of NumPy (&amp;lt;1.17), the way to create a new RNG is to use &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/legacy.html#numpy.random.RandomState&#34;&gt;&lt;code&gt;np.random.RandomState&lt;/code&gt;&lt;/a&gt; which is based on the popular Mersenne Twister 19937 algorithm. This is also how the global NumPy RNG is created. This function is still available in newer versions of NumPy, but it is now recommended to use &lt;code&gt;default_rng&lt;/code&gt; instead, which returns an instance of the statistically better &lt;a href=&#34;https://www.pcg-random.org&#34;&gt;PCG64&lt;/a&gt; RNG. You might still see &lt;code&gt;np.random.RandomState&lt;/code&gt; being used in tests as it has strong stability guarantees between different NumPy versions.&lt;/p&gt;
&lt;h2 id=&#34;random-number-generation-with-numpy&#34;&gt;Random number generation with NumPy&lt;a class=&#34;headerlink&#34; href=&#34;#random-number-generation-with-numpy&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you import &lt;code&gt;numpy&lt;/code&gt; in your Python script, an RNG is created behind the scenes. This RNG is the one used when you generate a new random value using a function such as &lt;code&gt;np.random.random&lt;/code&gt;. I will here refer to this RNG as the global NumPy RNG.&lt;/p&gt;
&lt;p&gt;Although not recommended, it is a common practice to reset the seed of this global RNG at the beginning of a script using the &lt;code&gt;np.random.seed&lt;/code&gt; function. Fixing the seed at the beginning ensures that the script is reproducible: the same values and results will be produced each time you run it. However, although sometimes convenient, using the global NumPy RNG is a bad practice. A simple reason is that using global variables can lead to undesired side effects. For instance one might use &lt;code&gt;np.random.random&lt;/code&gt; without knowing that the seed of the global RNG was set somewhere else in the codebase. Quoting &lt;a href=&#34;https://numpy.org/neps/nep-0019-rng-policy.html&#34;&gt;Numpy Enhancement Proposal (NEP) 19&lt;/a&gt; by Robert Kern:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The implicit global RandomState behind the &lt;code&gt;np.random.*&lt;/code&gt; convenience functions can cause problems, especially when threads or other forms of concurrency are involved. Global state is always problematic. We categorically recommend avoiding using the convenience functions when reproducibility is involved. [&amp;hellip;] The preferred best practice for getting reproducible pseudorandom numbers is to instantiate a generator object with a seed and pass it around.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In short:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instead of using &lt;code&gt;np.random.seed&lt;/code&gt;, which reseeds the already created global NumPy RNG, and then using &lt;code&gt;np.random.*&lt;/code&gt; functions, you should create a new RNG.&lt;/li&gt;
&lt;li&gt;You should create an RNG at the beginning of your script (with your own seed if you want reproducibility) and use this RNG in the rest of the script.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To create a new RNG you can use the &lt;code&gt;default_rng&lt;/code&gt; function as illustrated in the &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/index.html&#34;&gt;introduction of the random module documentation&lt;/a&gt;:&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;numpy&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;np&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;random&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;default_rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;random&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# generate a floating point number between 0 and 1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you want to use a seed for reproducibility, &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/index.html#quick-start&#34;&gt;the NumPy documentation&lt;/a&gt; recommends using a large random number, where large means at least 128 bits. The first reason for using a large random number is that this increases the probability of having a different seed than anyone else and thus independent results. The second reason is that relying only on small numbers for your seeds can lead to biases as they do not fully explore the state space of the RNG. This limitation implies that the first number generated by your RNG may not seem as random as expected due to inaccessible first internal states. For example, some numbers will never be produced as the first output. One possibility would be to pick the seed at random in the state space of the RNG but &lt;a href=&#34;https://github.com/numpy/numpy/issues/25778#issuecomment-1930441151&#34;&gt;according to Robert Kern&lt;/a&gt; a 128-bit random number is large enough&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. To generate a 128-bit random number for your seed you can rely on the &lt;a href=&#34;https://docs.python.org/3/library/secrets.html&#34;&gt;secrets module&lt;/a&gt;:&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;secrets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;secrets&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;randbits&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;128&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;When running this code I get &lt;code&gt;65647437836358831880808032086803839626&lt;/code&gt; for the number to use as my seed. This number is randomly generated so you need to copy paste the value that is returned by &lt;code&gt;secrets.randbits(128)&lt;/code&gt; otherwise you will have a different seed each time you run your code and thus break reproducibility:&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;numpy&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;np&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;seed&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;65647437836358831880808032086803839626&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;random&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;default_rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;seed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;random&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The reason for seeding your RNG only once (and passing that RNG around) is that with a good RNG such as the one returned by &lt;code&gt;default_rng&lt;/code&gt; you will be ensured good randomness and independence of the generated numbers. However, if not done properly, using several RNGs (each one created with its own seed) might lead to streams of random numbers that are less independent than the ones created from the same seed&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. That being said, &lt;a href=&#34;https://github.com/numpy/numpy/issues/15322#issuecomment-573890207&#34;&gt;as explained by Robert Kern&lt;/a&gt;, with the RNGs and seeding strategies introduced in NumPy 1.17, it is considered fairly safe to create RNGs using system entropy, i.e. using &lt;code&gt;default_rng(None)&lt;/code&gt; multiple times. However as explained later be careful when running jobs in parallel and relying on &lt;code&gt;default_rng(None)&lt;/code&gt;. Another reason for seeding your RNG only once is that obtaining a good seed can be time consuming. Once you have a good seed to instantiate your generator, you might as well use it.&lt;/p&gt;
&lt;h2 id=&#34;passing-a-numpy-rng-around&#34;&gt;Passing a NumPy RNG around&lt;a class=&#34;headerlink&#34; href=&#34;#passing-a-numpy-rng-around&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As you write functions that you will use on their own as well as in a more complex script it is convenient to be able to pass a seed or your already created RNG. The function &lt;code&gt;default_rng&lt;/code&gt; allows you to do this very easily. As written above, this function can be used to create a new RNG from your chosen seed, if you pass a seed to it, or from system entropy when passing &lt;code&gt;None&lt;/code&gt; but you can also pass an already created RNG. In this case the returned RNG is the one that you passed.&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;numpy&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;np&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;stochastic_function&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;high&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;random&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;default_rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;integers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;high&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can either pass an &lt;code&gt;int&lt;/code&gt; seed or your already created RNG to &lt;code&gt;stochastic_function&lt;/code&gt;. To be perfectly exact, the &lt;code&gt;default_rng&lt;/code&gt; function returns the exact same RNG passed to it for certain kind of RNGs such at the ones created with &lt;code&gt;default_rng&lt;/code&gt; itself. You can refer to the &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng&#34;&gt;&lt;code&gt;default_rng&lt;/code&gt; documentation&lt;/a&gt; for more details on the arguments that you can pass to this function&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&#34;parallel-processing&#34;&gt;Parallel processing&lt;a class=&#34;headerlink&#34; href=&#34;#parallel-processing&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You must be careful when using RNGs in conjunction with parallel processing. Let&amp;rsquo;s consider the context of Monte Carlo simulation: you have a random function returning random outputs and you want to generate these random outputs a lot of times, for instance to compute an empirical mean. If the function is expensive to compute, an easy solution to speed up the computation time is to resort to parallel processing. Depending on the parallel processing library or backend that you use different behaviors can be observed. For instance if you do not set the seed yourself it can be the case that forked Python processes use the same random seed, generated for instance from system entropy, and thus produce the exact same outputs which is a waste of computational resources. A very nice example illustrating this when using the Joblib parallel processing library is available &lt;a href=&#34;https://joblib.readthedocs.io/en/latest/auto_examples/parallel_random_state.html&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you fix the seed at the beginning of your main script for reproducibility and then pass your seeded RNG to each process to be run in parallel, most of the time this will not give you what you want as this RNG will be deep copied. The same results will thus be produced by each process. One of the solutions is to create as many RNGs as parallel processes with a different seed for each of these RNGs. The issue now is that you cannot choose the seeds as easily as you would think. When you choose two different seeds to instantiate two different RNGs how do you know that the numbers produced by these RNGs will appear as statistically independent?&lt;sup id=&#34;fnref1:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; The design of independent RNGs for parallel processes has been an important research question. See, for example, &lt;a href=&#34;https://www.sciencedirect.com/science/article/pii/S0378475416300829&#34;&gt;Random numbers for parallel computers: Requirements and methods, with emphasis on GPUs&lt;/a&gt; by L&amp;rsquo;Ecuyer et al. (2017) for a good summary of different methods.&lt;/p&gt;
&lt;p&gt;Starting with NumPy 1.17, it is now very easy to instantiate independent RNGs. Depending on the type of RNG you use, different strategies are available as documented in the &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/parallel.html&#34;&gt;Parallel random number generation section&lt;/a&gt; of the NumPy documentation. One of the strategies is to use &lt;code&gt;SeedSequence&lt;/code&gt; which is an algorithm that makes sure that poor input seeds are transformed into good initial RNG states. More precisely, this ensures that you will not have a degenerate behavior from your RNG and that the subsequent numbers will appear random and independent. Additionally, it ensures that close seeds are mapped to very different initial states, resulting in RNGs that are, with very high probability, independent of each other. You can refer to the documentation of &lt;a href=&#34;https://numpy.org/doc/stable/reference/random/parallel.html#seedsequence-spawning&#34;&gt;SeedSequence Spawning&lt;/a&gt; for examples on how to generate independent RNGs from a &lt;code&gt;SeedSequence&lt;/code&gt; or an existing RNG. I here show how to apply this to the &lt;a href=&#34;https://joblib.readthedocs.io/en/latest/auto_examples/parallel_random_state.html#fixing-the-random-state-to-obtain-deterministic-results&#34;&gt;joblib example&lt;/a&gt; mentioned above.&lt;/p&gt;


&lt;div class=&#34;highlight&#34;&gt;
  &lt;pre class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;numpy&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;np&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;joblib&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Parallel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;delayed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;stochastic_function&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;high&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;random&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;default_rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;integers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;high&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;size&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;seed&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;319929794527176038403653493598663843656&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# creating the RNG that is passed around.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;random&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;default_rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;seed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# create 5 independent RNGs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;child_rngs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;spawn&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# use 2 processes to run the stochastic_function 5 times with joblib&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;random_vector&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Parallel&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;n_jobs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;delayed&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;stochastic_function&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;rng&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;child_rng&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;child_rng&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;child_rngs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;random_vector&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;By using a fixed seed you always get the same results each time you run this code and by using &lt;code&gt;rng.spawn&lt;/code&gt; you have an independent RNG for each call to &lt;code&gt;stochastic_function&lt;/code&gt;. Note that here you could also spawn from a &lt;code&gt;SeedSequence&lt;/code&gt; that you would create with the seed instead of creating an RNG. However, in general you pass around an RNG therefore I only assume to have access to an RNG. Also note that spawning from an RNG is only possible from version 1.25 of NumPy&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I hope this blog post helped you understand the best ways to use NumPy RNGs. The new Numpy API gives you all the tools you need for that. The resources below are available for further reading. Finally, I would like to thank Pamphile Roy, Stefan van der Walt and Jarrod Millman for their great feedbacks and comments which contributed to greatly improve the original version of this blog post.&lt;/p&gt;
&lt;h2 id=&#34;resources&#34;&gt;Resources&lt;a class=&#34;headerlink&#34; href=&#34;#resources&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&#34;numpy-rngs&#34;&gt;Numpy RNGs&lt;a class=&#34;headerlink&#34; href=&#34;#numpy-rngs&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://numpy.org/doc/stable/reference/random/index.html&#34;&gt;The documentation of the NumPy random module&lt;/a&gt; is the best place to find information and where I found most of the information that I share here.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://numpy.org/neps/nep-0019-rng-policy.html&#34;&gt;The Numpy Enhancement Proposal (NEP) 19 on the Random Number Generator Policy&lt;/a&gt; which lead to the changes introduced in NumPy 1.17&lt;/li&gt;
&lt;li&gt;A &lt;a href=&#34;https://github.com/numpy/numpy/issues/15322&#34;&gt;NumPy issue&lt;/a&gt; about the &lt;code&gt;check_random_state&lt;/code&gt; function and RNG good practices, especially &lt;a href=&#34;https://github.com/numpy/numpy/issues/15322#issuecomment-573890207&#34;&gt;this comment&lt;/a&gt; by Robert Kern.&lt;/li&gt;
&lt;li&gt;Check also &lt;a href=&#34;https://github.com/numpy/numpy/issues/25778#issuecomment-1930441151&#34;&gt;this answer of Robert Kern&lt;/a&gt; to my question about what &lt;code&gt;SeedSequence&lt;/code&gt; can and cannot do. This also explains why it is recommended to use very large random numbers for seeds.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://scikit-learn.org/stable/faq.html#how-do-i-set-a-random-state-for-an-entire-execution&#34;&gt;How do I set a random_state for an entire execution?&lt;/a&gt; from the scikit-learn FAQ.&lt;/li&gt;
&lt;li&gt;There are &lt;a href=&#34;https://github.com/scientific-python/specs/pull/180&#34;&gt;ongoing discussions&lt;/a&gt; about uniformizing the APIs used by different libraries to seed RNGs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;rngs-in-general&#34;&gt;RNGs in general&lt;a class=&#34;headerlink&#34; href=&#34;#rngs-in-general&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.sciencedirect.com/science/article/pii/S0378475416300829&#34;&gt;Random numbers for parallel computers: Requirements and methods, with emphasis on GPUs&lt;/a&gt; by L&amp;rsquo;Ecuyer et al. (2017)&lt;/li&gt;
&lt;li&gt;To know more about the default RNG used in NumPy, named PCG, I recommend the &lt;a href=&#34;https://www.pcg-random.org/paper.html&#34;&gt;PCG paper&lt;/a&gt; which also contains lots of useful information about RNGs in general. The &lt;a href=&#34;https://www.pcg-random.org&#34;&gt;pcg-random.org website&lt;/a&gt; is also full of interesting information about RNGs.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;If you only need a seed for reproducibility and do not need independence with respect to others, say for a unit test, a small seed is perfectly fine.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34;&gt;
&lt;p&gt;A good RNG is expected to produce independent numbers for a given seed. However, the independence of sequences generated from two different seeds is not always guaranteed. For instance, it is possible that the sequence started with the second seed might quickly converge to an internal state also obtained by the first seed. This can result in both RNGs producing the same subsequent numbers, which would compromise the randomness expected from distinct seeds.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref1:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34;&gt;
&lt;p&gt;Before knowing about &lt;code&gt;default_rng&lt;/code&gt;, and before NumPy 1.17, I was using the scikit-learn function &lt;a href=&#34;https://scikit-learn.org/stable/modules/generated/sklearn.utils.check_random_state.html&#34;&gt;&lt;code&gt;check_random_state&lt;/code&gt;&lt;/a&gt; which is of course heavily used in the scikit-learn codebase. While writing this post I discovered that this function is now available in &lt;a href=&#34;https://github.com/scipy/scipy/blob/62d2af2e13280d29781585aa39a3c5a5dfdfba17/scipy/_lib/_util.py#L231&#34;&gt;scipy&lt;/a&gt;. A look at the docstring and/or the source code of this function will give you a good idea about what it does. The differences with &lt;code&gt;default_rng&lt;/code&gt; are that &lt;code&gt;check_random_state&lt;/code&gt; currently relies on &lt;code&gt;np.random.RandomState&lt;/code&gt; and that when &lt;code&gt;None&lt;/code&gt; is passed to &lt;code&gt;check_random_state&lt;/code&gt; then the function returns the already existing global NumPy RNG. The latter can be convenient because if you fix the seed of the global RNG before in your script using &lt;code&gt;np.random.seed&lt;/code&gt;, &lt;code&gt;check_random_state&lt;/code&gt; returns the generator that you seeded. However, as explained above, this is not the recommended practice and you should be aware of the risks and the side effects.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34;&gt;
&lt;p&gt;Before 1.25 you need to get the &lt;code&gt;SeedSequence&lt;/code&gt; from the RNG using the &lt;code&gt;_seed_seq&lt;/code&gt; private attribute of the underlying bit generator: &lt;code&gt;rng.bit_generator._seed_seq&lt;/code&gt;. You can then spawn from this &lt;code&gt;SeedSequence&lt;/code&gt; to get child seeds that will result in independent RNGs.&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content>
      <category term="Tutorials" scheme="https://blog.scientific-python.org/tags/" label="Tutorials" />
      <category term="Numpy" scheme="https://blog.scientific-python.org/tags/" label="Numpy" />
      <category term="Rng" scheme="https://blog.scientific-python.org/tags/" label="Rng" />
    </entry>
    <entry>
      <title>NumPy&#39;s first Developer in Residence: Sayed Adel</title>
      <id>https://blog.scientific-python.org/numpy/fellowship-program/</id>
      <updated>2022-12-01T00:00:00Z</updated>
      <published>2022-12-01T00:00:00Z</published>
      <link rel="alternate" href="https://blog.scientific-python.org/numpy/fellowship-program/?utm_source=atom_feed" type="text/html" />
      <content type="html" xml:base="https://blog.scientific-python.org/numpy/fellowship-program/">&lt;blockquote&gt;Introducing the NumPy Fellowship Program and our first
Developer in Residence, Sayed Adel, who will be working on performance
optimization.&lt;/blockquote&gt;&lt;p&gt;The NumPy team is excited to announce the launch of the NumPy Fellowship
Program and the appointment of Sayed Adel
(&lt;a href=&#34;https://github.com/seiko2plus&#34;&gt;@seiko2plus&lt;/a&gt;) as the first NumPy
Developer in Residence. This is a significant milestone in the history
of the project: for the first time, NumPy is in a position to use its
project funds to pay for a full year of maintainer time. We believe that
this will be an impactful program that will contribute to NumPy&amp;rsquo;s
long-term sustainability as a community-driven open source project.&lt;/p&gt;
&lt;p&gt;Sayed has been making major contributions to NumPy
since the start of 2020, in particular around computational performance.
He is the main author of the NumPy SIMD architecture (&lt;a href=&#34;https://numpy.org/neps/nep-0038-SIMD-optimizations.html&#34;&gt;&lt;em&gt;NEP
38&lt;/em&gt;&lt;/a&gt;,
&lt;a href=&#34;https://numpy.org/devdocs/reference/simd/index.html&#34;&gt;&lt;em&gt;docs&lt;/em&gt;&lt;/a&gt;),
generously shared his knowledge of SIMD instructions with the core
developer team, and helped integrate the work of various volunteer and
industry contributors in this area. As a result, we&amp;rsquo;ve been able to
expand support to multiple CPU architectures, integrating contributions
from IBM, Intel, Apple, and others, none of which would have been
possible without Sayed. Furthermore, when NumPy tentatively started
using C&#43;&#43; in 2021, Sayed was one of the proponents of the move and
helped with its implementation.&lt;/p&gt;
&lt;p&gt;The NumPy Steering Council sees Sayed&amp;rsquo;s appointment to this role as both
recognition of his past outstanding contributions as well as an
opportunity to continue improving NumPy&amp;rsquo;s computational performance. In
the next 12 months, we&amp;rsquo;d like to see Sayed focus on the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SIMD code maintenance,&lt;/li&gt;
&lt;li&gt;code review of SIMD contributions from others,&lt;/li&gt;
&lt;li&gt;performance-related features,&lt;/li&gt;
&lt;li&gt;sharing SIMD and C&#43;&#43; expertise with the team and growing a NumPy
sub-team around it,&lt;/li&gt;
&lt;li&gt;SIMD build system migration to Meson,&lt;/li&gt;
&lt;li&gt;and wherever else Sayed&amp;rsquo;s interests take him.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;I&amp;rsquo;m both happy and nervous: this is a great opportunity, but also a
great responsibility,&amp;rdquo; said Sayed in response to his appointment.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The funds for the NumPy Fellowship Program come from a partnership with
Tidelift and from individual donations. We sincerely thank both
Tidelift and everyone who donated to the project&amp;mdash;without you, this
program would not be possible! We also acknowledge the CPython
Developer-in-Residence and the Django Fellowship programs, which
served as inspiration for this program.&lt;/p&gt;
&lt;p&gt;Sayed officially starts as the NumPy Developer in Residence today, 1
December 2022. Already, we are thinking about opportunities beyond
this first year: we imagine &amp;ldquo;in residence&amp;rdquo; roles that focus on
developing, improving, and maintaining other parts of the NumPy
project (e.g., documentation, website, translations, contributor
experience, etc.). We look forward to this exciting new chapter of the
NumPy contributor community and will keep you posted on our progress.&lt;/p&gt;
</content>
      <category term="Numpy" scheme="https://blog.scientific-python.org/tags/" label="Numpy" />
      <category term="Developer-in-Residence" scheme="https://blog.scientific-python.org/tags/" label="Developer-in-Residence" />
    </entry>
    <entry>
      <title>NumPy Contributor Spotlight: Mukulika Pahari</title>
      <id>https://blog.scientific-python.org/numpy/mukulikapahari/</id>
      <updated>2022-04-12T00:00:00Z</updated>
      <published>2022-04-12T00:00:00Z</published>
      <link rel="alternate" href="https://blog.scientific-python.org/numpy/mukulikapahari/?utm_source=atom_feed" type="text/html" />
      <content type="html" xml:base="https://blog.scientific-python.org/numpy/mukulikapahari/">&lt;blockquote&gt;An interview with Mukulika Pahari, a NumPy contributor.&lt;/blockquote&gt;&lt;p&gt;Our first Contributor Spotlight interview is with Mukulika Pahari, our “go-to” person for Numpy documentation.
Mukulika is a Computer Science student at Mumbai University. Her passions outside of
computing involve things with paper, including reading books (fiction!), folding origami, and journaling.
During our interview she discussed why she joined NumPy, what keeps her motivated, and how likely
she would recommend becoming a NumPy contributor.&lt;/p&gt;
&lt;h2 id=&#34;tell-us-something-about-yourself&#34;&gt;Tell us something about yourself.&lt;a class=&#34;headerlink&#34; href=&#34;#tell-us-something-about-yourself&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hi, I am Mukulika. I live in Mumbai, India, and I’m completing my Computer Science degree at Mumbai University.
I joined NumPy last summer during Google Season of Docs. The idea behind this initiative is to raise awareness
of open source, the role of documentation, and the importance of technical writing. It also gives technical
writers an opportunity to gain experience working on open source projects.&lt;/p&gt;
&lt;p&gt;Apart from that, I like to read fiction &amp;ndash; literally everything that I can put my hands on &amp;ndash;
and I find it relaxing to learn origami from YouTube tutorials.&lt;/p&gt;
&lt;h2 id=&#34;what-is-your-role-in-the-numpy-project&#34;&gt;What is your role in the NumPy project?&lt;a class=&#34;headerlink&#34; href=&#34;#what-is-your-role-in-the-numpy-project&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I write technical documentation for NumPy, and I help new contributors with their questions.&lt;/p&gt;
&lt;h2 id=&#34;what-are-your-best-and-worst-experiences-contributing-to-numpy-or-other-open-source-projects&#34;&gt;What are your best and worst experiences contributing to NumPy or other open source projects?&lt;a class=&#34;headerlink&#34; href=&#34;#what-are-your-best-and-worst-experiences-contributing-to-numpy-or-other-open-source-projects&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The best part for me, honestly, is the people. It is inspiring to meet people from diverse backgrounds
all over the world and do something together. However, I do find it quite scary to put your code out
there for &amp;ldquo;the whole world to see and evaluate.&amp;rdquo; It can challenge my confidence. But meeting all
the contributors, seeing their work, and getting their valuable feedback is absolutely worth it.&lt;/p&gt;
&lt;h2 id=&#34;what-motivated-you-to-join-and-contribute-to-numpywhat-keeps-you-motivated&#34;&gt;What motivated you to join and contribute to NumPy?What keeps you motivated?&lt;a class=&#34;headerlink&#34; href=&#34;#what-motivated-you-to-join-and-contribute-to-numpywhat-keeps-you-motivated&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since I already used NumPy in my data analysis courses in school, and now I am using it at my internship,
I thought that I could also contribute to it. It is always more fun to do side projects in a group.
Once you get to know the people in the NumPy community, you want to stay. They are really open and supportive!&lt;/p&gt;
&lt;h2 id=&#34;what-is-the-book-youve-given-most-as-a-gift-and-why&#34;&gt;What is the book you’ve given most as a gift and why?&lt;a class=&#34;headerlink&#34; href=&#34;#what-is-the-book-youve-given-most-as-a-gift-and-why&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Well, I do not really give out books to people &amp;ndash; being a broke college student is quite a barrier.
But I think that everyone should read &amp;ldquo;The Hitchhiker&amp;rsquo;s Guide to Galaxy&amp;rdquo; by Douglas Adams. It is
absolutely hilarious! It is both entertaining and spiked with wisdom.&lt;/p&gt;
&lt;h2 id=&#34;what-small-purchase-has-most-impacted-your-life-recently&#34;&gt;What small purchase has most impacted your life recently?&lt;a class=&#34;headerlink&#34; href=&#34;#what-small-purchase-has-most-impacted-your-life-recently&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I recently bought a nice journal and started to write in it. I find it very cleansing to put thoughts
on paper and give them structure. I appreciate pretty paper products&amp;ndash;this one has pastel pages.&lt;/p&gt;
&lt;h2 id=&#34;how-has-a-failure-or-apparent-failure-set-you-up-for-later-success&#34;&gt;How has a failure, or apparent failure, set you up for later success?&lt;a class=&#34;headerlink&#34; href=&#34;#how-has-a-failure-or-apparent-failure-set-you-up-for-later-success&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I can&amp;rsquo;t think of a specific situation, but, in general, all my experiences so far seem
to follow a general theme: it is absolutely okay not to be great at everything.
You fail, and then you learn for the future.&lt;/p&gt;
&lt;h2 id=&#34;who-do-you-consider-a-successful-person&#34;&gt;Who do you consider a successful person?&lt;a class=&#34;headerlink&#34; href=&#34;#who-do-you-consider-a-successful-person&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My definition of success is being happy without causing harm to anyone.&lt;/p&gt;
&lt;h2 id=&#34;what-advice-would-you-give-someone-at-the-start-of-their-career-what-advice-should&#34;&gt;What advice would you give someone at the start of their career? What advice should&lt;a class=&#34;headerlink&#34; href=&#34;#what-advice-would-you-give-someone-at-the-start-of-their-career-what-advice-should&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;they ignore?
Since I am at the beginning of my career, I can&amp;rsquo;t say much. But I think it is nice
to listen to everyone and get feedback, with the mindset that you do not necessarily have
to act on their advice. Having multiple perspectives is good.&lt;/p&gt;
&lt;h2 id=&#34;how-likely-you-are-to-recommend-contributing-to-numpy-on-a-scale-of-zero-to-ten&#34;&gt;How likely you are to recommend contributing to NumPy on a scale of zero to ten?&lt;a class=&#34;headerlink&#34; href=&#34;#how-likely-you-are-to-recommend-contributing-to-numpy-on-a-scale-of-zero-to-ten&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;d say a solid nine! It is overall a great experience.&lt;/p&gt;
&lt;h2 id=&#34;is-there-something-you-wish-id-asked-but-havent&#34;&gt;Is there something you wish I’d asked but haven’t?&lt;a class=&#34;headerlink&#34; href=&#34;#is-there-something-you-wish-id-asked-but-havent&#34; title=&#34;Link to this heading&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yes. What I like the most about the NumPy community is that
it does not require huge commitments time-wise. Every little thing is appreciated,
so that is certainly motivating.&lt;/p&gt;
</content>
      <category term="Contributor-Spotlight" scheme="https://blog.scientific-python.org/tags/" label="Contributor-Spotlight" />
      <category term="Numpy" scheme="https://blog.scientific-python.org/tags/" label="Numpy" />
    </entry>
</feed>
