From 7fcae7b5cfad6509ca598011bb66ac48a6a0601c Mon Sep 17 00:00:00 2001
From: MarsBarLee <46167686+MarsBarLee@users.noreply.github.com>
Date: Wed, 8 Feb 2023 12:19:21 -0500
Subject: [PATCH 1/3] Add files
---
apps/labs/posts/hypothesis-array-api.md | 716 ++++++++++++++++++++++++
1 file changed, 716 insertions(+)
create mode 100644 apps/labs/posts/hypothesis-array-api.md
diff --git a/apps/labs/posts/hypothesis-array-api.md b/apps/labs/posts/hypothesis-array-api.md
new file mode 100644
index 000000000..f6f749b73
--- /dev/null
+++ b/apps/labs/posts/hypothesis-array-api.md
@@ -0,0 +1,716 @@
+---
+title: "Using Hypothesis to test array-consuming libraries"
+author: matthew-barber
+published: October 6, 2021
+description: 'This blog post is for anyone developing array-consuming methods (think SciPy and scikit-learn) and is new to property-based testing. I demonstrate a typical workflow of testing with Hypothesis whilst writing an array-consuming function that works for all libraries adopting the Array API, catching bugs before your users do.'
+category: [Array API]
+featuredImage:
+ src: /posts/hello-world-post/featured.png
+ alt: 'Excellent alt-text describing the featured image'
+hero:
+ imageSrc: /posts/hello-world-post/hero.jpeg
+ imageAlt: 'Excellent alt-text describing the hero image'
+---
+
+![Hypothesis logo accompanied by the text \"Property-based testing for
+the Array API\"](/images/2021/10/hypothesis-array-api-social.png)
+
+Over the summer, I\'ve been interning at Quansight Labs to develop
+testing tools for the developers and users of the upcoming [Array API
+standard](https://data-apis.org/array-api/latest/). Specifically, I
+contributed \"strategies\" to the testing library
+[Hypothesis](https://github.com/HypothesisWorks/hypothesis/), which I\'m
+excited to announce are now available in
+[`hypothesis.extra.array_api`](https://hypothesis.readthedocs.io/en/latest/numpy.html#array-api).
+Check out the primary [pull
+request](https://github.com/HypothesisWorks/hypothesis/pull/3065) I made
+for more background.
+
+This blog post is for anyone developing array-consuming methods (think
+SciPy and scikit-learn) and is new to property-based testing. I
+demonstrate a typical workflow of testing with Hypothesis whilst writing
+an array-consuming function that works for *all* [libraries adopting the
+Array
+API](https://data-apis.org/array-api/latest/purpose_and_scope.html#stakeholders),
+catching bugs before your users do.
+
+## Before we begin
+
+Hypothesis shipped with its Array API strategies in [version
+6.21](https://hypothesis.readthedocs.io/en/latest/changes.html#v6-21-0).
+We also need to use NumPy \>= 1.22 so that we can test with its
+[recently merged](https://github.com/numpy/numpy/pull/18585) Array API
+implementation---this hasn\'t been released just yet, so I would
+recommend installing a [nightly
+build](https://anaconda.org/scipy-wheels-nightly/numpy).
+
+I will be using the excellent
+[ipytest](https://github.com/chmp/ipytest/) extension to nicely run
+tests in Jupyter as if we were using
+[pytest](https://github.com/pytest-dev/pytest/) proper. For pretty
+printing I use the superb [Rich](https://github.com/willmcgugan/rich)
+library, where I simply override Python\'s builtin `print` with
+[`rich.print`](https://rich.readthedocs.io/en/stable/reference/init.html#rich.print).
+I also suppress all warnings for convenience\'s sake.
+
+``` python
+%%capture
+!pip install hypothesis>=6.21
+!pip install -i https://pypi.anaconda.org/scipy-wheels-nightly/simple numpy
+```
+
+``` python
+%%capture
+!pip install ipytest
+import ipytest; ipytest.autoconfig(display_columns=80)
+```
+
+``` python
+%%capture
+!pip install rich
+from rich import print
+```
+
+``` python
+import warnings; warnings.filterwarnings("ignore")
+```
+
+## What the Array API enables
+
+The [API](https://data-apis.org/array-api/latest/) standardises
+functionality of array libraries, which has [numerous
+benefits](https://data-apis.org/array-api/latest/use_cases.html) for
+both developers and users. I recommend reading the [Data APIs
+announcement
+post](https://data-apis.org/blog/announcing_the_consortium/) to get a
+better idea of how the API is being shaped, but for our purposes it
+works an awful lot like NumPy.
+
+The most exciting prospect for me is being able to easily write an
+array-consuming method that works with all the adopting libraries.
+Let\'s try writing this method to calculate the cumulative sums of an
+array:
+
+``` python
+def cumulative_sums(x):
+ """Return the cumulative sums of the elements of the input."""
+ xp = x.__array_namespace__()
+
+ result = xp.empty(x.size, dtype=x.dtype)
+ result[0] = x[0]
+ for i in range(1, x.size):
+ result[i] = result[i - 1] + x[i]
+
+ return result
+```
+
+The all-important
+[`__array_namespace__()`](https://data-apis.org/array-api/latest/API_specification/array_object.html#method-array-namespace)
+method allows array-consuming methods to get the array\'s respective
+Array API module. Conventionally we assign it to the variable `xp`.
+
+From there you just need to rely on the guarantees of the Array API to
+support NumPy, TensorFlow, PyTorch, CuPy, etc. all in one simple method!
+
+## Good ol\' unit tests
+
+I hope you\'d want write some tests at some point 😉
+
+We can import NumPy\'s Array API implementation and test with that for
+now, although in the future it\'d be a good idea to try other
+implementations (see [related Hypothesis
+issue](https://github.com/HypothesisWorks/hypothesis/issues/3085)). We
+don\'t `import numpy as np`, but instead import NumPy\'s new module
+`numpy.array_api`, which exists to comply with the Array API standard
+where `numpy` proper can not (namely so NumPy can keep backwards
+compatibility).
+
+``` python
+from numpy import array_api as nxp
+
+def test_cumulative_sums():
+ x = nxp.asarray([0, 1, 2, 3, 4])
+ assert nxp.all(cumulative_sums(x) == nxp.asarray([0, 1, 3, 6, 10]))
+
+ipytest.run()
+```
+
+ . [100%]
+ 1 passed in 0.02s
+
+I would probably write a
+[parametrized](https://docs.pytest.org/en/stable/parametrize.html) test
+here and write cases to cover all the interesting scenarios I can think
+of. Whatever we do, we will definitely miss some edge cases. What if we
+could catch bugs we would never think of ourselves?
+
+## Testing our assumptions with Hypothesis
+
+```{=html}
+
+```
+Hypothesis is a property-based testing library. To lift from their
+excellent
+[docs](https://hypothesis.readthedocs.io/en/latest/index.html), think of
+a normal unit test as being something like the following:
+
+1. Set up some data.
+2. Perform some operations on the data.
+3. Assert something about the result.
+
+Hypothesis lets you write tests which instead look like this:
+
+1. For all data matching some specification.
+2. Perform some operations on the data.
+3. Assert something about the result.
+
+You almost certainly will find new bugs with Hypothesis thanks to how it
+cleverly fuzzes your specifications, but the package really shines in
+how it [\"reduces\" failing test
+cases](https://drops.dagstuhl.de/opus/volltexte/2020/13170/) to present
+only the minimal reproducers that trigger said bugs. This demo will
+showcase both its power and user-friendliness.
+
+Let\'s try testing a simple assumption that we can make about our
+`cumulative_sums()` method:
+
+> For an array with positive elements, its cumulative sums should only
+> increment or remain the same per step.
+
+```{=html}
+
+```
+```{=html}
+
+```
+We can write a simple enough Hypothesis-powered test method for this:
+
+``` python
+from hypothesis import given
+from hypothesis.extra.array_api import make_strategies_namespace
+
+xps = make_strategies_namespace(nxp)
+
+@given(xps.arrays(dtype="uint8", shape=10))
+def test_positive_arrays_have_incrementing_sums(x):
+ a = cumulative_sums(x)
+ assert nxp.all(a[1:] >= a[:-1])
+```
+
+As the Array API tools provided by Hypothesis are agnostic to the
+adopting array/tensor libraries, we first need to bind an implementation
+via
+[`make_strategies_namespace()`](https://hypothesis.readthedocs.io/en/latest/numpy.html#hypothesis.extra.array_api.make_strategies_namespace).
+Passing `numpy.array_api` will give us a
+[`SimpleNamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace)
+to use these tools for NumPy\'s Array API implementation.
+
+The
+[`@given()`](https://hypothesis.readthedocs.io/en/latest/details.html#hypothesis.given)
+decorator tells Hypothesis what values it should generate for our test
+method. In this case
+[`xps.arrays()`](https://hypothesis.readthedocs.io/en/latest/numpy.html#xps.arrays)
+is a \"search strategy\" that specifies Array API-compliant arrays from
+`numpy.array_api` should be generated.
+
+In this case, `shape=10` specifies the arrays generated are
+1-dimensional and of size 10, and `dtype="uint8"` specifies they should
+contain unsigned integers (which is handy for our test method as uints
+are always positive). Let\'s quickly see a small sample of the arrays
+Hypothesis can generate:
+
+``` python
+for _ in range(10):
+ x = xps.arrays(dtype="uint8", shape=10, unique=True).example()
+ print(repr(x))
+print("...")
+```
+
+```{=html}
+
Array([239, 211, 226, 129, 31, 13, 80, 235, 254, 163], dtype=uint8)
+
+```
+
+```{=html}
+Array([164, 175, 254, 111, 63, 241, 64, 201, 173, 117], dtype=uint8)
+
+```
+
+```{=html}
+Array([106, 149, 210, 230, 58, 37, 66, 153, 203, 181], dtype=uint8)
+
+```
+
+```{=html}
+Array([ 93, 0, 254, 253, 252, 251, 250, 249, 248, 247], dtype=uint8)
+
+```
+
+```{=html}
+Array([ 16, 0, 254, 253, 252, 251, 250, 249, 248, 247], dtype=uint8)
+
+```
+
+```{=html}
+Array([172, 0, 254, 253, 252, 251, 250, 249, 248, 247], dtype=uint8)
+
+```
+
+```{=html}
+Array([129, 0, 254, 253, 252, 251, 250, 249, 248, 247], dtype=uint8)
+
+```
+
+```{=html}
+Array([111, 0, 254, 253, 252, 251, 250, 249, 248, 247], dtype=uint8)
+
+```
+
+```{=html}
+Array([ 67, 0, 254, 253, 252, 251, 250, 249, 248, 247], dtype=uint8)
+
+```
+
+```{=html}
+Array([ 0, 255, 254, 253, 252, 251, 250, 249, 248, 247], dtype=uint8)
+
+```
+
+```{=html}
+...
+
+```
+
+How Hypothesis \"draws\" from its strategies can look rather
+unremarkable at first. A small sample of draws might look fairly uniform
+but trust that strategies will end up covering all kinds of edge cases.
+Importantly it will cover these cases efficiently so that
+Hypothesis-powered tests are *relatively* quick to run on your machine.
+
+All our test method does is get the cumulative sums array `a` that is
+returned from `cumulative_sums(x)`, and then check that every element
+`a[i]` is greater than or equal to `a[i-1]`.
+
+Time to run it!
+
+``` python
+ipytest.run("-k positive_arrays_have_incrementing_sums", "--hypothesis-seed=3")
+```
+
+ F [100%]
+ =================================== FAILURES ===================================
+ _________________ test_positive_arrays_have_incrementing_sums __________________
+
+ @given(xps.arrays(dtype="uint8", shape=10))
+ > def test_positive_arrays_have_incrementing_sums(x):
+
+ :7:
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+ x = Array([26, 26, 26, 26, 26, 26, 26, 26, 26, 26], dtype=uint8)
+
+ @given(xps.arrays(dtype="uint8", shape=10))
+ def test_positive_arrays_have_incrementing_sums(x):
+ a = cumulative_sums(x)
+ > assert nxp.all(a[1:] >= a[:-1])
+ E assert Array(False, dtype=bool)
+ E + where Array(False, dtype=bool) = (Array([ 52, 78, 104, 130, 156, 182, 208, 234, 4], dtype=uint8) >= Array([ 26, 52, 78, 104, 130, 156, 182, 208, 234], dtype=uint8))
+ E + where = nxp.all
+
+ :9: AssertionError
+ ---------------------------------- Hypothesis ----------------------------------
+ Falsifying example: test_positive_arrays_have_incrementing_sums(
+ x=Array([26, 26, 26, 26, 26, 26, 26, 26, 26, 26], dtype=uint8),
+ )
+ =========================== short test summary info ============================
+ FAILED ::test_positive_arrays_have_incrementing_sums - assert A...
+ 1 failed, 1 deselected in 0.17s
+
+Hypothesis has tested our assumption and told us we\'re wrong. It
+provides us with the following falsifying example:
+
+``` python
+>>> x = xp.full(10, 26, dtype=xp.uint8)
+>>> x
+Array([ 26, 26, 26, 26, 26, 26, 26, 26, 26, 26], dtype=uint8)
+>>> cumulative_sums(x)
+Array([ 26, 52, 78, 104, 130, 156, 182, 208, 234, 4], dtype=uint8)
+```
+
+You can see that an overflow error has occurred for the final cumulative
+sum, as 234 + 26 (260) cannot be represented in 8-bit unsigned integers.
+
+Let\'s try promoting the dtype of the cumulative sums array so that it
+can represent larger numbers, and then we can run the test again.
+
+``` python
+def max_dtype(xp, dtype):
+ if dtype in [getattr(xp, name) for name in ("int8", "int16", "int32", "int64")]:
+ return xp.int64
+ elif dtype in [getattr(xp, name) for name in ("uint8", "uint16", "uint32", "uint64")]:
+ return xp.uint64
+ else:
+ return xp.float64
+
+def cumulative_sums(x):
+ xp = x.__array_namespace__()
+
+ result = xp.empty(x.size, dtype=max_dtype(xp, x.dtype))
+ result[0] = x[0]
+ for i in range(1, x.size):
+ result[i] = result[i - 1] + x[i]
+
+ return result
+
+ipytest.run("-k positive_arrays_have_incrementing_sums")
+```
+
+ . [100%]
+ 1 passed, 1 deselected in 0.18s
+
+You can see another assumption about our code is:
+
+> We can find the cumulative sums of arrays of any scalar dtype.
+
+We should cover this assumption in our test method
+`test_positive_arrays_have_incrementing_sums` by passing child search
+strategies into our
+[`xps.arrays()`](https://hypothesis.readthedocs.io/en/latest/numpy.html#xps.arrays)
+parent strategy. Specifying `dtype` as
+[`xps.scalar_dtypes()`](https://hypothesis.readthedocs.io/en/latest/numpy.html#xps.scalar_dtypes)
+will tell Hypothesis to generate arrays of all scalar dtypes. To specify
+that these array values should be positive, we can just pass keyword
+arguments to the underlying value generating strategy
+[`xps.from_dtype()`](https://hypothesis.readthedocs.io/en/latest/numpy.html#xps.from_dtype)
+via `elements={"min_value": 0}`.
+
+And while we\'re at it, let\'s make sure to cover another assumption:
+
+> We can find the cumulative sums of arrays with multiple dimensions.
+
+Specifying `shape` as
+[`xps.array_shapes()`](https://hypothesis.readthedocs.io/en/latest/numpy.html#xps.array_shapes)
+will tell Hypothesis to generate arrays of various dimensionality and
+sizes. We can
+[filter](https://hypothesis.readthedocs.io/en/latest/data.html#filtering)
+this strategy with `lambda s: prod(s) > 1` so that always `x.size > 1`,
+allowing our test code to still work.
+
+``` python
+from math import prod
+from hypothesis import settings
+
+@given(
+ xps.arrays(
+ dtype=xps.scalar_dtypes(),
+ shape=xps.array_shapes().filter(lambda s: prod(s) > 1),
+ elements={"min_value": 0},
+ )
+)
+def test_positive_arrays_have_incrementing_sums(x):
+ a = cumulative_sums(x)
+ assert nxp.all(a[1:] >= a[:-1])
+
+ipytest.run("-k positive_arrays_have_incrementing_sums", "--hypothesis-seed=3")
+```
+
+ F [100%]
+ =================================== FAILURES ===================================
+ _________________ test_positive_arrays_have_incrementing_sums __________________
+
+ @given(
+ > xps.arrays(
+ dtype=xps.scalar_dtypes(),
+ shape=xps.array_shapes().filter(lambda s: prod(s) > 1),
+ elements={"min_value": 0},
+ )
+ )
+ E hypothesis.errors.MultipleFailures: Hypothesis found 2 distinct failures.
+
+ :5: MultipleFailures
+ ---------------------------------- Hypothesis ----------------------------------
+ Falsifying example: test_positive_arrays_have_incrementing_sums(
+ x=Array([[False, False]], dtype=bool),
+ )
+ TypeError: only size-1 arrays can be converted to Python scalars
+
+ The above exception was the direct cause of the following exception:
+
+ Traceback (most recent call last):
+ , line 12, in test_positive_arrays_have_incrementing_sums
+ a = cumulative_sums(x)
+ , line 13, in cumulative_sums
+ result[0] = x[0]
+ File "/numpy/array_api/_array_object.py", line 657, in __setitem__
+ self._array.__setitem__(key, asarray(value)._array)
+ ValueError: setting an array element with a sequence.
+
+ Falsifying example: test_positive_arrays_have_incrementing_sums(
+ x=Array([False, False], dtype=bool),
+ )
+ Traceback (most recent call last):
+ , line 12, in test_positive_arrays_have_incrementing_sums
+ a = cumulative_sums(x)
+ , line 15, in cumulative_sums
+ result[i] = result[i - 1] + x[i]
+ File "/numpy/array_api/_array_object.py", line 362, in __add__
+ other = self._check_allowed_dtypes(other, "numeric", "__add__")
+ File "/numpy/array_api/_array_object.py", line 125, in _check_allowed_dtypes
+ raise TypeError(f"Only {dtype_category} dtypes are allowed in {op}")
+ TypeError: Only numeric dtypes are allowed in __add__
+ =========================== short test summary info ============================
+ FAILED ::test_positive_arrays_have_incrementing_sums - hypothes...
+ 1 failed, 1 deselected in 0.38s
+
+Again Hypothesis has proved our assumptions wrong, and this time it\'s
+found two problems.
+
+Firstly, our `cumulative_sums()` method doesn\'t adjust for boolean
+arrays, so we get an error when we add two `bool` values together.
+
+``` python
+>>> x = xp.zeros(2, dtype=xp.bool)
+>>> x
+Array([False, False], dtype=bool)
+>>> cumulative_sums(x)
+Traceback:
+ , line 15, in cumulative_sums
+ result[i] = result[i - 1] + x[i]
+ ...
+TypeError: Only numeric dtypes are allowed in __add__
+```
+
+Secondly, our `cumulative_sums()` method is assuming arrays are
+1-dimensional, so we get an error when we wrongly assume `x[0]` will
+always return a single scalar (technically a 0-dimensional array).
+
+``` python
+>>> x = xp.zeros((1, 2), dtype=xp.bool)
+>>> x
+Array([[False, False]], dtype=bool)
+>>> cumulative_sums(x)
+Traceback:
+ , line 13, in cumulative_sums
+ result[0] = x[0]
+ ...
+TypeError: only size-1 arrays can be converted to Python scalars
+```
+
+I\'m going to flatten input arrays and convert the boolean arrays to
+integer arrays of ones and zeros. Of-course we\'ll run the test again to
+make sure our updated `cumulative_sums()` method now works.
+
+``` python
+def cumulative_sums(x):
+ xp = x.__array_namespace__()
+
+ x = xp.reshape(x, x.size)
+
+ if x.dtype == xp.bool:
+ mask = x
+ dtype = xp.uint64
+ x = xp.zeros(x.shape, dtype=xp.uint64)
+ x[mask] = 1
+
+ result = xp.empty(x.size, dtype=max_dtype(xp, x.dtype))
+ result[0] = x[0]
+ for i in range(1, x.size):
+ result[i] = result[i - 1] + x[i]
+
+ return result
+
+ipytest.run("-k positive_arrays_have_incrementing_sums", "--hypothesis-seed=3")
+```
+
+ F [100%]
+ =================================== FAILURES ===================================
+ _________________ test_positive_arrays_have_incrementing_sums __________________
+
+ @given(
+ > xps.arrays(
+ dtype=xps.scalar_dtypes(),
+ shape=xps.array_shapes().filter(lambda s: prod(s) > 1),
+ elements={"min_value": 0},
+ )
+ )
+
+ :5:
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+ x = Array([4611686018427387904, 4611686018427387904], dtype=int64)
+
+ @given(
+ xps.arrays(
+ dtype=xps.scalar_dtypes(),
+ shape=xps.array_shapes().filter(lambda s: prod(s) > 1),
+ elements={"min_value": 0},
+ )
+ )
+ def test_positive_arrays_have_incrementing_sums(x):
+ a = cumulative_sums(x)
+ > assert nxp.all(a[1:] >= a[:-1])
+ E assert Array(False, dtype=bool)
+ E + where Array(False, dtype=bool) = (Array([-9223372036854775808], dtype=int64) >= Array([4611686018427387904], dtype=int64))
+ E + where = nxp.all
+
+ :13: AssertionError
+ ---------------------------------- Hypothesis ----------------------------------
+ Falsifying example: test_positive_arrays_have_incrementing_sums(
+ x=Array([4611686018427387904, 4611686018427387904], dtype=int64),
+ )
+ =========================== short test summary info ============================
+ FAILED ::test_positive_arrays_have_incrementing_sums - assert A...
+ 1 failed, 1 deselected in 1.24s
+
+We resolved our two previous issues\... but Hypothesis has found yet
+another failing scenario 🙃
+
+``` python
+>>> x = xp.full(2, 4611686018427387904, dtype=xp.int64)
+>>> x
+Array([ 4611686018427387904, 4611686018427387904], dtype=int64)
+>>> cumulative_sums(x)
+Array([ 4611686018427387904, -9223372036854775808], dtype=int64)
+```
+
+An overflow has occurred again, which we can\'t do much about it this
+time. There\'s no larger signed integer dtype than `int64` (in the Array
+API), so we\'ll just have `cumulative_sums()` detect overflows itself.
+
+``` python
+def cumulative_sums(x):
+ xp = x.__array_namespace__()
+
+ x = xp.reshape(x, x.size)
+
+ if x.dtype == xp.bool:
+ mask = x
+ dtype = xp.uint64
+ x = xp.zeros(x.shape, dtype=xp.uint64)
+ x[mask] = 1
+
+ result = xp.empty(x.size, dtype=max_dtype(xp, x.dtype))
+ result[0] = x[0]
+ for i in range(1, x.size):
+ result[i] = result[i - 1] + x[i]
+ if result[i] < result[i - 1]:
+ raise OverflowError("Cumulative sum cannot be represented")
+
+ return result
+```
+
+If Hypothesis generates arrays which raise `OverflowError`, we can just
+catch it and use
+[`assume(False)`](https://hypothesis.readthedocs.io/en/latest/details.html#making-assumptions)
+to ignore testing these arrays on runtime. This \"filter-on-runtime\"
+behaviour can be very handy at times, although [their docs note
+`assume()` can be
+problematic](https://hypothesis.readthedocs.io/en/latest/details.html#how-good-is-assume).
+
+We can also explicitly cover overflows in a separate test.
+
+``` python
+from hypothesis import assume
+import pytest
+
+@given(
+ xps.arrays(
+ dtype=xps.scalar_dtypes(),
+ shape=xps.array_shapes().filter(lambda s: prod(s) > 1),
+ elements={"min_value": 0},
+ )
+)
+def test_positive_arrays_have_incrementing_sums(x):
+ try:
+ a = cumulative_sums(x)
+ assert nxp.all(a[1:] >= a[:-1])
+ except OverflowError:
+ assume(False)
+
+def test_error_on_overflow():
+ x = nxp.asarray([nxp.iinfo(nxp.uint64).max, 1], dtype=nxp.uint64)
+ with pytest.raises(OverflowError):
+ cumulative_sums(x)
+
+ipytest.run()
+```
+
+ ... [100%]
+ 3 passed in 0.27s
+
+Our little test suite finally passes 😅
+
+If you\'re feeling adventurous, you might want to get [this very
+notebook](https://github.com/Quansight-Labs/quansight-labs-site/tree/main/posts/2021/10/hypothesis-array-api.ipynb)
+running and see if you can write some test cases yourself---bonus points
+if they fail! For starters, how about testing that cumulative sums
+*decrease* with arrays containing negative elements?
+
+When you\'re developing an Array API array-consuming method, and an
+equivalent method already exists for one of the adopting libraries, I
+highly recommend using Hypothesis to compare its results to your own.
+For example, we could use the battle-tested
+[`np.cumsum()`](https://numpy.org/doc/stable/reference/generated/numpy.cumsum.html)
+to see how our `cumulative_sums()` method compares:
+
+``` python
+import numpy as np
+
+@given(xps.arrays(dtype=xps.scalar_dtypes(), shape=xps.array_shapes()))
+def test_reference_implementation(x):
+ our_out = cumulative_sums(x)
+ # We convert numpy.array_api arrays into NumPy's top-level "ndarray"
+ # structure, so we can compare our results with np.cumsum().
+ # We do this via np.asarray(obj), which will see if obj supports
+ # NumPy's interface protocol to subsequently get the underlying
+ # ndarray from obj - fortunately arrays generated from
+ # numpy.array_api do support this!
+ # See https://numpy.org/devdocs/user/basics.interoperability.html
+ our_out = np.asarray(our_out)
+ their_out = np.cumsum(np.asarray(x))
+ assert np.all(our_out == their_out)
+```
+
+```{=html}
+
+```
+Such \"differential testing\" is a great exercise to really think about
+what your code does, even if ultimately you conclude that you are happy
+with different results.
+
+Zac Hatfield-Dodds, who maintains Hypothesis, writes more about
+differential testing in their short paper [\"Falsify your Software:
+validating scientific code with property-based
+testing\"](http://conference.scipy.org/proceedings/scipy2020/zac_hatfield-dodds.html).
+Generally it\'s a great read if you want more ideas on how Hypothesis
+can make your array-consuming libraries robust!
+
+## Watch this space
+
+This year should see a first
+[version](https://data-apis.org/array-api/latest/future_API_evolution.html#versioning)
+of the Array API standard, and subsequently NumPy shipping
+`numpy.array_api` out to the world---this means array-consuming
+libraries will be able to reliably develop for the Array API quite soon.
+I hope I\'ve demonstrated why you should try Hypothesis when the time
+comes 🙂
+
+Good news is that I\'m extending my stay at Quansight. My job is to help
+unify Python\'s fragmented scientific ecosystem, so I\'m more than happy
+to respond to any inquiries about using Hypothesis for the Array API via
+[email](mailto:quitesimplymatt@gmail.com) or
+[Twitter](https://twitter.com/whostolehonno).
+
+For now I\'m contributing to the Hypothesis-powered [Array API
+compliance suite](https://github.com/data-apis/array-api-tests), which
+is already being used by the NumPy team to ensure `numpy.array_api`
+actually complies with every tiny detail of the
+[specification](https://data-apis.org/array-api/latest/). This process
+has the added side-effect of finding limitations in
+[`hypothesis.extra.array_api`](https://hypothesis.readthedocs.io/en/latest/numpy.html#array-api),
+so you can expect Hypothesis to only improve from here on out!
+
From 719b38a6cd2f456c5a2cbd23e9897508f12a8fba Mon Sep 17 00:00:00 2001
From: MarsBarLee <46167686+MarsBarLee@users.noreply.github.com>
Date: Thu, 9 Feb 2023 13:09:56 -0500
Subject: [PATCH 2/3] Update image file path
---
apps/labs/posts/hypothesis-array-api.md | 13 +++++++------
.../hypothesis-array-api-social.png | Bin 0 -> 62737 bytes
2 files changed, 7 insertions(+), 6 deletions(-)
create mode 100644 apps/labs/public/posts/hypothesis-array-api/hypothesis-array-api-social.png
diff --git a/apps/labs/posts/hypothesis-array-api.md b/apps/labs/posts/hypothesis-array-api.md
index f6f749b73..a836c31fb 100644
--- a/apps/labs/posts/hypothesis-array-api.md
+++ b/apps/labs/posts/hypothesis-array-api.md
@@ -5,15 +5,16 @@ published: October 6, 2021
description: 'This blog post is for anyone developing array-consuming methods (think SciPy and scikit-learn) and is new to property-based testing. I demonstrate a typical workflow of testing with Hypothesis whilst writing an array-consuming function that works for all libraries adopting the Array API, catching bugs before your users do.'
category: [Array API]
featuredImage:
- src: /posts/hello-world-post/featured.png
- alt: 'Excellent alt-text describing the featured image'
+ src: /posts/hypothesis-array-api/hypothesis-array-api-social.png
+ alt: "Hypothesis logo accompanied by the text 'Property-based testing for
+the Array API'"
hero:
- imageSrc: /posts/hello-world-post/hero.jpeg
- imageAlt: 'Excellent alt-text describing the hero image'
+ imageSrc: /posts/hypothesis-array-api/hypothesis-array-api-social.png
+ imageAlt: "Hypothesis logo accompanied by the text 'Property-based testing for
+the Array API'"
---
-![Hypothesis logo accompanied by the text \"Property-based testing for
-the Array API\"](/images/2021/10/hypothesis-array-api-social.png)
+![](/images/2021/10/hypothesis-array-api-social.png)
Over the summer, I\'ve been interning at Quansight Labs to develop
testing tools for the developers and users of the upcoming [Array API
diff --git a/apps/labs/public/posts/hypothesis-array-api/hypothesis-array-api-social.png b/apps/labs/public/posts/hypothesis-array-api/hypothesis-array-api-social.png
new file mode 100644
index 0000000000000000000000000000000000000000..92be45e98d59dfea44087a0acd032f454db61220
GIT binary patch
literal 62737
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 | | | | | | | | | | | | | | |