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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 obu^=Lad8p0w6n(DF|{`n#yVQW&s~Is1QnH Date: Thu, 9 Feb 2023 13:42:05 -0500 Subject: [PATCH 3/3] Update hero and feature images --- apps/labs/posts/hypothesis-array-api.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/labs/posts/hypothesis-array-api.md b/apps/labs/posts/hypothesis-array-api.md index a836c31fb..8b44c90ea 100644 --- a/apps/labs/posts/hypothesis-array-api.md +++ b/apps/labs/posts/hypothesis-array-api.md @@ -6,16 +6,12 @@ description: 'This blog post is for anyone developing array-consuming methods (t category: [Array API] featuredImage: src: /posts/hypothesis-array-api/hypothesis-array-api-social.png - alt: "Hypothesis logo accompanied by the text 'Property-based testing for -the Array API'" + alt: 'Hypothesis logo accompanied by the text Property-based testing for the Array API' hero: imageSrc: /posts/hypothesis-array-api/hypothesis-array-api-social.png - imageAlt: "Hypothesis logo accompanied by the text 'Property-based testing for -the Array API'" + imageAlt: '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