codecov-io: # [Codecov](https://codecov.io/gh/sdepy/sdepy/pull/12?src=pr&el=h1) Report
> Merging [#12](https://codecov.io/gh/sdepy/sdepy/pull/12?src=pr&el=desc) into [master](https://codecov.io/gh/sdepy/sdepy/commit/47ffea9072ee6cb5f33a1a8e98db0063fab18634?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `82.35%`.

[![Impacted file tree graph](https://codecov.io/gh/sdepy/sdepy/pull/12/graphs/tree.svg?width=650&token=sXNigQjlFY&height=150&src=pr)](https://codecov.io/gh/sdepy/sdepy/pull/12?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #12      +/-   ##
==========================================
+ Coverage   91.57%   91.58%   +<.01%     
==========================================
  Files           7        7              
  Lines        1875     1877       +2     
  Branches      271      272       +1     
==========================================
+ Hits         1717     1719       +2     
  Misses         96       96              
  Partials       62       62
```


| [Impacted Files](https://codecov.io/gh/sdepy/sdepy/pull/12?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sdepy/infrastructure.py](https://codecov.io/gh/sdepy/sdepy/pull/12/diff?src=pr&el=tree#diff-c2RlcHkvaW5mcmFzdHJ1Y3R1cmUucHk=) | `88.75% <82.35%> (+0.02%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/sdepy/sdepy/pull/12?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sdepy/sdepy/pull/12?src=pr&el=footer). Last update [47ffea9...28ad4f7](https://codecov.io/gh/sdepy/sdepy/pull/12?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/sdepy/infrastructure.py b/sdepy/infrastructure.py
index 122f02b..dc873b3 100644
--- a/sdepy/infrastructure.py
+++ b/sdepy/infrastructure.py
@@ -289,7 +289,7 @@ class process(np.ndarray):
     A process is a subclass of numpy.ndarray, where its values as an array
     are the process values along the timeline and across paths. All
     numpy.ndarray methods, attributes and properties are guaranteed to act
-    upon such values, as would those of the parent class. Such no-overriding
+    upon such values, as would those of the parent class. Such no overriding
     commitment is intended to safeguard predictablity of array operations
     on process instances; process-specific functionalities are delegated
     to process-specific methods, attributes and properties.
@@ -464,30 +464,38 @@ class process(np.ndarray):
                         'timelines'.format(a.shape, self.shape))
         return out_array
 
-    def __array_wrap__(self, out_array, context):
-        ufunc, inputs, domain = context
-        assert hasattr(self, 't')
-        assert any(self is a for a in inputs)
-
-        # get process inputs
-        p_inputs = [a for a in inputs
-                    if isinstance(a, process)]
-
-        # ??? overcautious - to be eliminated
-        for a in p_inputs:
-            if not self._is_compatible(a):
-                assert False, 'this should never occur - '\
-                       '__array_prepare__ should enforce compatibility'
-
-        # set t to the common non constant timeline
-        # or to the constant timeline of the first input
-        t = p_inputs[0].t
-        for a in p_inputs[1:]:
-            if len(a.t) > 1:
-                t = a.t
-                break
-        cls = type(self)
-        return cls(t=t, x=out_array)
+    def __array_wrap__(self, out_array, context=None):
+        if context is None:
+            # this may happen since numpy 1.16.0 when a process instance
+            # invokes a numpy.ndarray method (eg. sum, mean, etc.):
+            # in such case the resulting out_array is returned, as
+            # needed to comply with the no overriding commitment
+            # for numpy.ndarray methods
+            return out_array
+        else:
+            ufunc, inputs, domain = context
+            assert hasattr(self, 't')
+            assert any(self is a for a in inputs)
+
+            # get process inputs
+            p_inputs = [a for a in inputs
+                        if isinstance(a, process)]
+
+            # ??? overcautious - to be eliminated
+            for a in p_inputs:
+                if not self._is_compatible(a):
+                    assert False, 'this should never occur - '\
+                           '__array_prepare__ should enforce compatibility'
+
+            # set t to the common non constant timeline
+            # or to the constant timeline of the first input
+            t = p_inputs[0].t
+            for a in p_inputs[1:]:
+                if len(a.t) > 1:
+                    t = a.t
+                    break
+            cls = type(self)
+            return cls(t=t, x=out_array)
 
     # -------------
     # interpolation
@@ -1095,14 +1103,14 @@ class process(np.ndarray):
 def piecewise(t=0., *, x=None, v=None, dtype=None, mode='mid'):
     """
     Return a process that interpolates to a piecewise constant function.
-    
+
     Parameters
     ----------
     t : array-like
         Reference timeline (see below).
     x : array-like, optional
         Values of the process along the timeline and across paths.
-        One and only one of ``x``, ``v``, must be provided, 
+        One and only one of ``x``, ``v``, must be provided,
         as a keyword argument.
     v : array-like, optional
         Values of a deterministic (one path) process along the timeline.
@@ -1113,22 +1121,22 @@ def piecewise(t=0., *, x=None, v=None, dtype=None, mode='mid'):
         to the reference timeline: 'mid', 'forward', 'backward'
         set ``t[i]`` to be the midpoint, start or end point respectively,
         of the constant segment with value ``x[i]`` or ``v[i]``.
-    
+
     See Also
     --------
     process
-    
+
     Notes
     -----
     Parameters ``t``, ``x``, ``v``, ``dtype`` conform to the ``process``
     instantiation interface and shape requirements.
-    
+
     The returned process ``p`` behaves as advertised upon interpolation
-    with default interpolation kind, and may be used 
-    as a time dependent piecewise constant parameter in SDE integration. 
-    However, its timeline ``p.t`` and values ``p.x`` 
-    are not guaranteed to coincide with the given ``t`` or ``x``, 
-    and should not be relied upon.    
+    with default interpolation kind, and may be used
+    as a time dependent piecewise constant parameter in SDE integration.
+    However, its timeline ``p.t`` and values ``p.x``
+    are not guaranteed to coincide with the given ``t`` or ``x``,
+    and should not be relied upon.
     """
     # delegate preprocessing of arguments to the process class
     p = process(t, x=x, v=v, dtype=dtype)

Failing tests with numpy versions >= 1.16.0
Since numpy 1.16.0 several ``numpy.ndarray`` methods returning an array (eg. ``sum``, ``mean`` etc.), that may be considered as unary ufuncs, pass the result through the ``__array_wrap__`` method (up to numpy 1.15.4, no such call was issued).

When invoked in this way, ``process.__array_wrap__`` fails, raising exceptions.

diff --git a/sdepy/tests/test_process.py b/sdepy/tests/test_process.py
index 6ca77a7..26bea8d 100644
--- a/sdepy/tests/test_process.py
+++ b/sdepy/tests/test_process.py
@@ -605,6 +605,16 @@ def test_summary():
         b = np.cumsum(p, axis=0)
         assert_allclose(a, b, rtol=eps(p.dtype))
 
+    # numpy summary operations
+    for f in funcs:
+        a = getattr(p, f)()
+        b = getattr(np, f)(p)
+        c = getattr(np, f)(p.x)
+        assert_(isinstance(a, np.ndarray) and not isinstance(a, process))
+        assert_(isinstance(b, np.ndarray) and not isinstance(b, process))
+        assert_allclose(a, c)
+        assert_allclose(b, c)
+
 
 # ----------------------------------------
 # test increments, derivative and integral
@@ -714,7 +724,7 @@ def test_piecewise():
         p = piecewise((1, 2), v=(10, 20), mode='zzz')
     with assert_warns(DeprecationWarning):
         p = _piecewise_constant_process((1, 2), v=(10, 20))
-        
+
 
 # case testing
 def tst_piecewise(dtype, paths, vshape, mode, shift):
@@ -728,7 +738,7 @@ def tst_piecewise(dtype, paths, vshape, mode, shift):
     else:
         x = xx = 1 + np.random.random((3,) + vshape + (paths,)).astype(dtype)
         vv = None
-        
+
     if mode is None:
         p = piecewise(t, x=xx, v=vv)  # default mode is 'mid'
     else:

pip install -e .
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
Babel==2.14.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
docutils==0.19
exceptiongroup==1.2.2
idna==3.10
imagesize==1.4.1
importlib-metadata==6.7.0
iniconfig==2.0.0
Jinja2==3.1.6
MarkupSafe==2.1.5
nose==1.3.7
numpy==1.21.6
numpydoc==1.5.0
packaging==24.0
pluggy==1.2.0
Pygments==2.17.2
pytest==7.4.4
pytz==2025.2
requests==2.31.0
scipy==1.7.3
-e git+https://github.com/sdepy/sdepy.git@47ffea9072ee6cb5f33a1a8e98db0063fab18634#egg=sdepy
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
tomli==2.0.1
typing_extensions==4.7.1
urllib3==2.0.7
zipp==3.15.0

name: sdepy
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - babel==2.14.0
      - charset-normalizer==3.4.1
      - docutils==0.19
      - exceptiongroup==1.2.2
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - jinja2==3.1.6
      - markupsafe==2.1.5
      - nose==1.3.7
      - numpy==1.21.6
      - numpydoc==1.5.0
      - packaging==24.0
      - pluggy==1.2.0
      - pygments==2.17.2
      - pytest==7.4.4
      - pytz==2025.2
      - requests==2.31.0
      - scipy==1.7.3
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - zipp==3.15.0
prefix: /opt/conda/envs/sdepy

sdepy/tests/test_process.py::test_constructor sdepy/tests/test_process.py::test_interp sdepy/tests/test_process.py::test_t_getitem sdepy/tests/test_process.py::test_p_getitem sdepy/tests/test_process.py::test_v_getitem sdepy/tests/test_process.py::test_properties sdepy/tests/test_process.py::test_summary sdepy/tests/test_process.py::test_increments
sdepy/tests/test_process.py::test_import sdepy/tests/test_process.py::test_broadcasting sdepy/tests/test_process.py::test_interp_values sdepy/tests/test_process.py::test_shapeas sdepy/tests/test_process.py::test_copy sdepy/tests/test_process.py::test_chf_cdf sdepy/tests/test_process.py::test_piecewise
sdepy/infrastructure.py
sdepy/infrastructure.py