Repository: scikit-hep/awkward

Issue:
Unexpected behaviour when broadcasting single value in ak.with_field
It seems something is going wrong when letting `ak.with_field` broadcast a single value (like `True`). When i create yet another field, there are circumstances where this breaks the previously added field:

```pycon
>>> import awkward1 as ak
>>> a = ak.Array([[{"x" : 0.1, "y" : 0.2, "z" : 0.3}, {"x" : 0.4, "y" : 0.5, "z" : 0.6}]])
>>> a["always_true"] = True
>>> ak.to_list(a)
[[{'x': 0.1, 'y': 0.2, 'z': 0.3, 'always_true': True}, {'x': 0.4, 'y': 0.5, 'z': 0.6, 'always_true': True}]]
>>> a["sometimes_true"] = a.x > 0.3 # this breaks the "always_true" field
>>> ak.to_list(a)
[[{'x': 0.1, 'y': 0.2, 'z': 0.3, 'always_true': False, 'sometimes_true': False}, {'x': 0.4, 'y': 0.5, 'z': 0.6, 'always_true': False, 'sometimes_true': True}]]
```

It seems to only happen in cases where the value is broadcasted by creating that stride 0 memory view @jpivarski explained in https://github.com/scikit-hep/awkward-1.0/pull/275#pullrequestreview-417815324:

https://github.com/scikit-hep/awkward-1.0/blob/7e1001b6ee59f1cba96cf57d144e7f2719f07e69/src/awkward1/operations/structure.py#L477-L481





Discussion:
There are two safer alternatives:

   * replace the `as_strided` call with `numpy.broadcast_to([what], shape=(len(base),))`, which ought to be the same thing, but it's not in NumPy's "danger zone" (i.e. maybe I'm missing something important about making a zero-stride array);
   * replace the `as_strided` with something that actually creates the array, such as `numpy.repeat(what, len(base))`.

If the first one works, then there was some subtlety in making zero-stride arrays that I didn't know about but `broadcast_to` gets right. Anyway, this should be preferred because it's higher-level code.

If the second one works, then performance suffers, but probably not enough to notice, compared to other operations, and it's better to be correct than fast.
The first alternative doesn't help, there seems to be a general problem with the stride 0 view. The second alternative seems to help in this case.
Should such memory views work in general? If there are no other places in the code that rely on this then probably here it also should be just an  actual array?
In principle it should work, but the zero-stridedness is not maintained after Awkward broadcasting (unlike NumPy broadcasting, Awkward broadcasting does have to copy elements to fit them in to unequal-length lists). So just calling `numpy.repeat` here is not giving up much.

Would you be willing to add this as a PR, since it works in your local repo?

Resolving patch:
diff --git a/src/awkward1/operations/structure.py b/src/awkward1/operations/structure.py
--- a/src/awkward1/operations/structure.py
+++ b/src/awkward1/operations/structure.py
@@ -475,9 +475,7 @@ def getfunction(inputs, depth):
             if isinstance(base, awkward1.layout.RecordArray):
                 if not isinstance(what, awkward1.layout.Content):
                     what = awkward1.layout.NumpyArray(
-                        numpy.lib.stride_tricks.as_strided(
-                            [what], shape=(len(base),), strides=(0,)
-                        )
+                        numpy.repeat(what, len(base))
                     )
                 return lambda: (base.setitem_field(where, what),)
             else:


Test patch:
diff --git a/tests/test_0286-broadcast-single-value-with-field.py b/tests/test_0286-broadcast-single-value-with-field.py
new file mode 100644
--- /dev/null
+++ b/tests/test_0286-broadcast-single-value-with-field.py
@@ -0,0 +1,17 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/master/LICENSE
+
+from __future__ import absolute_import
+
+import sys
+
+import pytest
+import numpy
+
+import awkward1
+
+def test_broadcast_single_bool():
+    base = awkward1.Array([[{"x" : 0.1, "y" : 0.2, "z" : 0.3}, {"x" : 0.4, "y" : 0.5, "z" : 0.6}]])
+    base_new1 = awkward1.with_field(base, True, "always_true")
+    assert awkward1.to_list(base_new1.always_true) == [[True, True]]
+    base_new2 = awkward1.with_field(base_new1, base.x > 0.3, "sometimes_true")
+    assert awkward1.to_list(base_new2.always_true) == [[True, True]]