<p dir="auto">This is something similar to <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="287601442" data-permission-text="Title is private" data-url="https://github.com/numpy/numpy/issues/10363" data-hovercard-type="issue" data-hovercard-url="/numpy/numpy/issues/10363/hovercard" href="https://github.com/numpy/numpy/issues/10363">#10363</a>.</p>
<p dir="auto">When feeding a <code class="notranslate">float</code> to a numpy function, you get a <code class="notranslate">numpy.float64</code>. When feeding an array of rank &gt; 0 to a numpy function, you get an array of the same rank. UNLESS the array is rank 0, i.e., a scalar for most intents and purposes. Then you get a <code class="notranslate">numpy.float64</code>.</p>
<p dir="auto">MWE:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import numpy

print(type(numpy.sqrt(2.0)))                  # numpy.float64, okay
print(type(numpy.sqrt(numpy.array([2.0]))))   # numpy.ndarray, okay
print(type(numpy.sqrt(numpy.array(2.0))))     # numpy.float64, gnrg"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-s1">numpy</span>

<span class="pl-en">print</span>(<span class="pl-en">type</span>(<span class="pl-s1">numpy</span>.<span class="pl-en">sqrt</span>(<span class="pl-c1">2.0</span>)))                  <span class="pl-c"># numpy.float64, okay</span>
<span class="pl-en">print</span>(<span class="pl-en">type</span>(<span class="pl-s1">numpy</span>.<span class="pl-en">sqrt</span>(<span class="pl-s1">numpy</span>.<span class="pl-en">array</span>([<span class="pl-c1">2.0</span>]))))   <span class="pl-c"># numpy.ndarray, okay</span>
<span class="pl-en">print</span>(<span class="pl-en">type</span>(<span class="pl-s1">numpy</span>.<span class="pl-en">sqrt</span>(<span class="pl-s1">numpy</span>.<span class="pl-en">array</span>(<span class="pl-c1">2.0</span>))))     <span class="pl-c"># numpy.float64, gnrg</span></pre></div>
<p dir="auto">It'd be more consistent if the numpy functions returned an array if you feed it it one, even for rank 0.</p>
<p dir="auto">If only given 0d inputs, even if they are of type <code class="notranslate">ndarray</code>, ufuncs will decay their output to a scalar via <code class="notranslate">[()]</code> (as noted in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="30352675" data-permission-text="Title is private" data-url="https://github.com/numpy/numpy/issues/4563" data-hovercard-type="issue" data-hovercard-url="/numpy/numpy/issues/4563/hovercard" href="https://github.com/numpy/numpy/issues/4563">#4563</a>, <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="72454838" data-permission-text="Title is private" data-url="https://github.com/numpy/numpy/issues/5819" data-hovercard-type="issue" data-hovercard-url="/numpy/numpy/issues/5819/hovercard" href="https://github.com/numpy/numpy/issues/5819">#5819</a>). While we can't change this behavior now without creating signficant pain downstream, we could add a way to opt out of it.</p>
<p dir="auto"><a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="417893639" data-permission-text="Title is private" data-url="https://github.com/numpy/numpy/issues/13100" data-hovercard-type="issue" data-hovercard-url="/numpy/numpy/issues/13100/hovercard" href="https://github.com/numpy/numpy/issues/13100">#13100</a> raises a case where <code class="notranslate">np.fix</code> resorts to calling <code class="notranslate">np.asanyarray(np.ceil(x, out=out))</code> in order to ensure that the result is an array. Unfortunately, this has a number of draw-backs:</p>
<ul dir="auto">
<li>It discards duck-types, like <code class="notranslate">dask</code> arrays</li>
<li>It changes the dtype of 0d object arrays containing array-likes</li>
</ul>
<p dir="auto">Proposed implementation:</p>
<ul dir="auto">
<li>Create a new <code class="notranslate">np.leave_wrapped</code> sentinel object that can be passed as an <code class="notranslate">out</code> argument</li>
<li>Add support in <code class="notranslate">ufunc.__call__</code>, <code class="notranslate">np.take</code>, ... for passing <code class="notranslate">out=np.leave_wrapped</code> meaning "do not call PyArray_Return", causing the result to never be a scalar</li>
<li>Expose <code class="notranslate">PyArray_Return</code> to python as <code class="notranslate">np.core.unpack_scalar</code></li>
<li>Implement <code class="notranslate">np.fix</code> as:</li>
</ul>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="def fix(x, out=None):
    if out is None:
        do_unwrap = True
        out = np.leave_wrapped
    else:
        do_unwrap = False
    res = nx.ceil(x, out=out)
    res = nx.floor(x, out=res, where=nx.greater_equal(x, 0, out=np.leave_wrapped))

    if do_unwrap:
        res = np.unpack_scalar(res)   # PyArray_Return
    return res"><pre class="notranslate"><span class="pl-k">def</span> <span class="pl-en">fix</span>(<span class="pl-s1">x</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-c1">None</span>):
    <span class="pl-k">if</span> <span class="pl-s1">out</span> <span class="pl-c1">is</span> <span class="pl-c1">None</span>:
        <span class="pl-s1">do_unwrap</span> <span class="pl-c1">=</span> <span class="pl-c1">True</span>
        <span class="pl-s1">out</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-s1">leave_wrapped</span>
    <span class="pl-k">else</span>:
        <span class="pl-s1">do_unwrap</span> <span class="pl-c1">=</span> <span class="pl-c1">False</span>
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">nx</span>.<span class="pl-en">ceil</span>(<span class="pl-s1">x</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-s1">out</span>)
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">nx</span>.<span class="pl-en">floor</span>(<span class="pl-s1">x</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-s1">res</span>, <span class="pl-s1">where</span><span class="pl-c1">=</span><span class="pl-s1">nx</span>.<span class="pl-en">greater_equal</span>(<span class="pl-s1">x</span>, <span class="pl-c1">0</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-s1">np</span>.<span class="pl-s1">leave_wrapped</span>))

    <span class="pl-k">if</span> <span class="pl-s1">do_unwrap</span>:
        <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">unpack_scalar</span>(<span class="pl-s1">res</span>)   <span class="pl-c"># PyArray_Return</span>
    <span class="pl-k">return</span> <span class="pl-s1">res</span></pre></div>
<details>
<summary>
<p dir="auto">Original <code class="notranslate">unpack_scalars=True</code>  proposal</p>
</summary>
<ul dir="auto">
<li>Add a new <code class="notranslate">unpack_scalars=True</code> kwarg to <code class="notranslate">ufunc.__call__</code>, <code class="notranslate">ufunc.reduce</code>. When <code class="notranslate">False</code>, the current behavior of going through <code class="notranslate">PyArray_Return</code> is disabled. Alternative names:
<ul dir="auto">
<li><code class="notranslate">decay=True</code></li>
<li><code class="notranslate">unpack_0d=True</code></li>
<li><code class="notranslate">unpack_0d_ndarray=True</code> (<code class="notranslate">PyArray_Return</code> already does not apply to subclasses)</li>
</ul>
</li>
<li>Add a new <code class="notranslate">np.unpack_scalar(arr)</code> function to expose <code class="notranslate">PyArray_Return</code> to python code. This would <strong>not</strong> be overloadable with <code class="notranslate">__array_function__</code>, since existing uses of <code class="notranslate">PyArray_Return</code> are also not.</li>
</ul>
<p dir="auto">With these changes, the current implementation of <code class="notranslate">np.fix</code> would change from:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="def fix(x, out=None):
    res = nx.asanyarray(nx.ceil(x, out=out))
    res = nx.floor(x, out=res, where=nx.greater_equal(x, 0))

    if out is None and type(res) is nx.ndarray:
        res = res[()]
    return res"><pre class="notranslate"><span class="pl-k">def</span> <span class="pl-en">fix</span>(<span class="pl-s1">x</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-c1">None</span>):
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">nx</span>.<span class="pl-en">asanyarray</span>(<span class="pl-s1">nx</span>.<span class="pl-en">ceil</span>(<span class="pl-s1">x</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-s1">out</span>))
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">nx</span>.<span class="pl-en">floor</span>(<span class="pl-s1">x</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-s1">res</span>, <span class="pl-s1">where</span><span class="pl-c1">=</span><span class="pl-s1">nx</span>.<span class="pl-en">greater_equal</span>(<span class="pl-s1">x</span>, <span class="pl-c1">0</span>))

    <span class="pl-k">if</span> <span class="pl-s1">out</span> <span class="pl-c1">is</span> <span class="pl-c1">None</span> <span class="pl-c1">and</span> <span class="pl-en">type</span>(<span class="pl-s1">res</span>) <span class="pl-c1">is</span> <span class="pl-s1">nx</span>.<span class="pl-s1">ndarray</span>:
        <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">res</span>[()]
    <span class="pl-k">return</span> <span class="pl-s1">res</span></pre></div>
<p dir="auto">to</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="def fix(x, out=None, *, unpack_scalars=True):
    res = nx.ceil(x, out=out, unpack_scalars=False)
    res = nx.floor(x, out=res, where=nx.greater_equal(x, 0, unpack_scalars=False), unpack_scalars=False)

    if unpack_scalars and out is None:
        res = np.unpack_scalar(res)
    return res"><pre class="notranslate"><span class="pl-k">def</span> <span class="pl-en">fix</span>(<span class="pl-s1">x</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-c1">None</span>, <span class="pl-c1">*</span>, <span class="pl-s1">unpack_scalars</span><span class="pl-c1">=</span><span class="pl-c1">True</span>):
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">nx</span>.<span class="pl-en">ceil</span>(<span class="pl-s1">x</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-s1">out</span>, <span class="pl-s1">unpack_scalars</span><span class="pl-c1">=</span><span class="pl-c1">False</span>)
    <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">nx</span>.<span class="pl-en">floor</span>(<span class="pl-s1">x</span>, <span class="pl-s1">out</span><span class="pl-c1">=</span><span class="pl-s1">res</span>, <span class="pl-s1">where</span><span class="pl-c1">=</span><span class="pl-s1">nx</span>.<span class="pl-en">greater_equal</span>(<span class="pl-s1">x</span>, <span class="pl-c1">0</span>, <span class="pl-s1">unpack_scalars</span><span class="pl-c1">=</span><span class="pl-c1">False</span>), <span class="pl-s1">unpack_scalars</span><span class="pl-c1">=</span><span class="pl-c1">False</span>)

    <span class="pl-k">if</span> <span class="pl-s1">unpack_scalars</span> <span class="pl-c1">and</span> <span class="pl-s1">out</span> <span class="pl-c1">is</span> <span class="pl-c1">None</span>:
        <span class="pl-s1">res</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">unpack_scalar</span>(<span class="pl-s1">res</span>)
    <span class="pl-k">return</span> <span class="pl-s1">res</span></pre></div>
<details>
If needed, I could promote this to an NEP </details></details>