We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
String broadcasting is not producing sensible results
### Version of Awkward Array

main

### Description and code to reproduce

As Jim and I discussed today, string broadcasting is currently not producing reliable results for string-string, or even string-non string cases. In particular many broadcasts violate the appearance that strings are atoms:

```python3
>>> import awkward as ak
>>> x = ak._v2.Array([["one", "two"], ["three", "four"]])
>>> ak._v2.broadcast_arrays(x[1:], x[:-1])
ValueError: while calling (from <ipython-input-42-6e4db831c1ff>, line 1)
    ak._v2.broadcast_arrays(
        arrays = (<Array [['one', 'two']] type='1 * var * string'>, <Array [[...
        kwargs = {}
    )
Error details: cannot broadcast nested list (in compiled code: https://github.com/scikit-hep/awkward-1.0/blob/1.10.0rc1/src/cpu-kernels/awkward_ListArray_broadcast_tooffsets.cpp#L27)
```

In this case, broadcasting an Array of strings against an Array of integers produces arrays that have the same structure as though the outer `__array__ = "string"` were missing (i.e. broadcasting happens against the underlying characters array):
```python3
>>> import awkward as ak
>>> x = ak._v2.Array([["one", "two"], ["three", "four"]])
>>> y = ak._v2.Array([[1,2],[3, 4]])
>>> ak._v2.broadcast_arrays(x, y)
[<Array [['one', 'two'], ['three', 'four']] type='2 * var * string'>,
 <Array [[[1, 1, 1], [2, 2, 2]], [[...], ...]] type='2 * var * var * int64'>]
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/awkward/operations/ak_broadcast_arrays.py`
Content:
```
1 # BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE
2 __all__ = ("broadcast_arrays",)
3 import awkward as ak
4 from awkward._backends.dispatch import backend_of
5 from awkward._backends.numpy import NumpyBackend
6 from awkward._behavior import behavior_of
7 from awkward._connect.numpy import unsupported
8 from awkward._layout import wrap_layout
9 from awkward._nplikes.numpylike import NumpyMetadata
10 
11 np = NumpyMetadata.instance()
12 cpu = NumpyBackend.instance()
13 
14 
15 def broadcast_arrays(
16     *arrays,
17     depth_limit=None,
18     broadcast_parameters_rule="one_to_one",
19     left_broadcast=True,
20     right_broadcast=True,
21     highlevel=True,
22     behavior=None,
23 ):
24     """
25     Args:
26         arrays: Array-like data (anything #ak.to_layout recognizes).
27         depth_limit (None or int, default is None): If None, attempt to fully
28             broadcast the `arrays` to all levels. If an int, limit the number
29             of dimensions that get broadcasted. The minimum value is `1`,
30             for no broadcasting.
31         broadcast_parameters_rule (str): Rule for broadcasting parameters, one of:
32             - `"intersect"`
33             - `"all_or_nothing"`
34             - `"one_to_one"`
35             - `"none"`
36         left_broadcast (bool): If True, follow rules for implicit
37             left-broadcasting, as described below.
38         right_broadcast (bool): If True, follow rules for implicit
39             right-broadcasting, as described below.
40         highlevel (bool, default is True): If True, return an #ak.Array;
41             otherwise, return a low-level #ak.contents.Content subclass.
42         behavior (None or dict): Custom #ak.behavior for the output array, if
43             high-level.
44 
45     Like NumPy's
46     [broadcast_arrays](https://docs.scipy.org/doc/numpy/reference/generated/numpy.broadcast_arrays.html)
47     function, this function returns the input `arrays` with enough elements
48     duplicated that they can be combined element-by-element.
49 
50     For NumPy arrays, this means that scalars are replaced with arrays with
51     the same scalar value repeated at every element of the array, and regular
52     dimensions are created to increase low-dimensional data into
53     high-dimensional data.
54 
55     For example,
56 
57         >>> ak.broadcast_arrays(5,
58         ...                     [1, 2, 3, 4, 5])
59         [<Array [5, 5, 5, 5, 5] type='5 * int64'>,
60          <Array [1, 2, 3, 4, 5] type='5 * int64'>]
61 
62     and
63 
64         >>> ak.broadcast_arrays(np.array([1, 2, 3]),
65         ...                     np.array([[0.1, 0.2, 0.3], [10, 20, 30]]))
66         [<Array [[  1,   2,   3], [ 1,  2,  3]] type='2 * 3 * int64'>,
67          <Array [[0.1, 0.2, 0.3], [10, 20, 30]] type='2 * 3 * float64'>]
68 
69     Note that in the second example, when the `3 * int64` array is expanded
70     to match the `2 * 3 * float64` array, it is the deepest dimension that
71     is aligned. If we try to match a `2 * int64` with the `2 * 3 * float64`,
72 
73         >>> ak.broadcast_arrays(np.array([1, 2]),
74         ...                     np.array([[0.1, 0.2, 0.3], [10, 20, 30]]))
75         ValueError: while calling
76             ak.broadcast_arrays(
77                 arrays = (array([1, 2]), array([[ 0.1,  0.2,  0.3],
78                [10. , 20....
79                 depth_limit = None
80                 broadcast_parameters_rule = 'one_to_one'
81                 left_broadcast = True
82                 right_broadcast = True
83                 highlevel = True
84                 behavior = None
85             )
86         Error details: cannot broadcast RegularArray of size 2 with RegularArray of size 3
87 
88     NumPy has the same behavior: arrays with different numbers of dimensions
89     are aligned to the right before expansion. One can control this by
90     explicitly adding a new axis (reshape to add a dimension of length 1)
91     where the expansion is supposed to take place because a dimension of
92     length 1 can be expanded like a scalar.
93 
94         >>> ak.broadcast_arrays(np.array([1, 2])[:, np.newaxis],
95         ...                     np.array([[0.1, 0.2, 0.3], [10, 20, 30]]))
96         [<Array [[  1,   1,   1], [ 2,  2,  2]] type='2 * 3 * int64'>,
97          <Array [[0.1, 0.2, 0.3], [10, 20, 30]] type='2 * 3 * float64'>]
98 
99     Again, NumPy does the same thing (`np.newaxis` is equal to None, so this
100     trick is often shown with None in the slice-tuple). Where the broadcasting
101     happens can be controlled, but numbers of dimensions that don't match are
102     implicitly aligned to the right (fitting innermost structure, not
103     outermost).
104 
105     While that might be an arbitrary decision for rectilinear arrays, it is
106     much more natural for implicit broadcasting to align left for tree-like
107     structures. That is, the root of each data structure should agree and
108     leaves may be duplicated to match. For example,
109 
110         >>> ak.broadcast_arrays([            100,   200,        300],
111         ...                     [[1.1, 2.2, 3.3],    [], [4.4, 5.5]])
112         [<Array [[100, 100, 100], [], [300, 300]] type='3 * var * int64'>,
113          <Array [[1.1, 2.2, 3.3], [], [4.4, 5.5]] type='3 * var * float64'>]
114 
115     One typically wants single-item-per-element data to be duplicated to
116     match multiple-items-per-element data. Operations on the broadcasted
117     arrays like
118 
119         one_dimensional + nested_lists
120 
121     would then have the same effect as the procedural code
122 
123         for x, outer in zip(one_dimensional, nested_lists):
124             output = []
125             for inner in outer:
126                 output.append(x + inner)
127             yield output
128 
129     where `x` has the same value for each `inner` in the inner loop.
130 
131     Awkward Array's broadcasting manages to have it both ways by applying the
132     following rules:
133 
134     * If all dimensions are regular (i.e. #ak.types.RegularType), like NumPy,
135       implicit broadcasting aligns to the right, like NumPy.
136     * If any dimension is variable (i.e. #ak.types.ListType), which can
137       never be true of NumPy, implicit broadcasting aligns to the left.
138     * Explicit broadcasting with a length-1 regular dimension always
139       broadcasts, like NumPy.
140 
141     Thus, it is important to be aware of the distinction between a dimension
142     that is declared to be regular in the type specification and a dimension
143     that is allowed to be variable (even if it happens to have the same length
144     for all elements). This distinction is can be accessed through the
145     #ak.Array.type, but it is lost when converting an array into JSON or
146     Python objects.
147 
148     If arrays have the same depth but different lengths of nested
149     lists, attempting to broadcast them together is a broadcasting error.
150 
151         >>> one = ak.Array([[[1, 2, 3], [], [4, 5], [6]], [], [[7, 8]]])
152         >>> two = ak.Array([[[1.1, 2.2], [3.3], [4.4], [5.5]], [], [[6.6]]])
153         >>> ak.broadcast_arrays(one, two)
154         ValueError: while calling
155             ak.broadcast_arrays(
156                 arrays = (<Array [[[1, 2, 3], [], [4, ...], [6]], ...] type='3 * var ...
157                 depth_limit = None
158                 broadcast_parameters_rule = 'one_to_one'
159                 left_broadcast = True
160                 right_broadcast = True
161                 highlevel = True
162                 behavior = None
163             )
164         Error details: cannot broadcast nested list
165 
166     For this, one can set the `depth_limit` to prevent the operation from
167     attempting to broadcast what can't be broadcasted.
168 
169         >>> this, that = ak.broadcast_arrays(one, two, depth_limit=1)
170         >>> this.show()
171         [[[1, 2, 3], [], [4, 5], [6]],
172          [],
173          [[7, 8]]]
174         >>> that.show()
175         [[[1.1, 2.2], [3.3], [4.4], [5.5]],
176          [],
177          [[6.6]]]
178     """
179     with ak._errors.OperationErrorContext(
180         "ak.broadcast_arrays",
181         {
182             "arrays": arrays,
183             "depth_limit": depth_limit,
184             "broadcast_parameters_rule": broadcast_parameters_rule,
185             "left_broadcast": left_broadcast,
186             "right_broadcast": right_broadcast,
187             "highlevel": highlevel,
188             "behavior": behavior,
189         },
190     ):
191         return _impl(
192             arrays,
193             depth_limit,
194             broadcast_parameters_rule,
195             left_broadcast,
196             right_broadcast,
197             highlevel,
198             behavior,
199         )
200 
201 
202 def _impl(
203     arrays,
204     depth_limit,
205     broadcast_parameters_rule,
206     left_broadcast,
207     right_broadcast,
208     highlevel,
209     behavior,
210 ):
211     # Need at least one array!
212     if len(arrays) == 0:
213         return []
214 
215     backend = backend_of(*arrays, default=cpu)
216 
217     inputs = []
218     for x in arrays:
219         y = ak.operations.to_layout(x, allow_record=True, allow_other=True)
220         if not isinstance(y, (ak.contents.Content, ak.Record)):
221             y = ak.contents.NumpyArray(backend.nplike.asarray([y]))
222         inputs.append(y.to_backend(backend))
223 
224     def action(inputs, depth, **kwargs):
225         if depth == depth_limit or all(x.is_numpy for x in inputs):
226             return tuple(inputs)
227         else:
228             return None
229 
230     behavior = behavior_of(*arrays, behavior=behavior)
231     out = ak._broadcasting.broadcast_and_apply(
232         inputs,
233         action,
234         behavior,
235         left_broadcast=left_broadcast,
236         right_broadcast=right_broadcast,
237         broadcast_parameters_rule=broadcast_parameters_rule,
238         numpy_to_regular=True,
239     )
240     assert isinstance(out, tuple)
241     return [wrap_layout(x, behavior, highlevel) for x in out]
242 
243 
244 @ak._connect.numpy.implements("broadcast_arrays")
245 def _nep_18_impl(*args, subok=unsupported):
246     return broadcast_arrays(*args)
247
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/awkward/operations/ak_broadcast_arrays.py b/src/awkward/operations/ak_broadcast_arrays.py
--- a/src/awkward/operations/ak_broadcast_arrays.py
+++ b/src/awkward/operations/ak_broadcast_arrays.py
@@ -222,7 +222,14 @@
         inputs.append(y.to_backend(backend))
 
     def action(inputs, depth, **kwargs):
-        if depth == depth_limit or all(x.is_numpy for x in inputs):
+        # The depth limit is the depth at which we must return, i.e.
+        # the _first_ layout at that depth
+        if depth == depth_limit:
+            return tuple(inputs)
+        # Walk through non-leaf nodes
+        elif all(
+            x.purelist_depth == 1 and not (x.is_option or x.is_indexed) for x in inputs
+        ):
             return tuple(inputs)
         else:
             return None

{"golden_diff": "diff --git a/src/awkward/operations/ak_broadcast_arrays.py b/src/awkward/operations/ak_broadcast_arrays.py\n--- a/src/awkward/operations/ak_broadcast_arrays.py\n+++ b/src/awkward/operations/ak_broadcast_arrays.py\n@@ -222,7 +222,14 @@\n         inputs.append(y.to_backend(backend))\n \n     def action(inputs, depth, **kwargs):\n-        if depth == depth_limit or all(x.is_numpy for x in inputs):\n+        # The depth limit is the depth at which we must return, i.e.\n+        # the _first_ layout at that depth\n+        if depth == depth_limit:\n+            return tuple(inputs)\n+        # Walk through non-leaf nodes\n+        elif all(\n+            x.purelist_depth == 1 and not (x.is_option or x.is_indexed) for x in inputs\n+        ):\n             return tuple(inputs)\n         else:\n             return None\n", "issue": "String broadcasting is not producing sensible results\n### Version of Awkward Array\r\n\r\nmain\r\n\r\n### Description and code to reproduce\r\n\r\nAs Jim and I discussed today, string broadcasting is currently not producing reliable results for string-string, or even string-non string cases. In particular many broadcasts violate the appearance that strings are atoms:\r\n\r\n```python3\r\n>>> import awkward as ak\r\n>>> x = ak._v2.Array([[\"one\", \"two\"], [\"three\", \"four\"]])\r\n>>> ak._v2.broadcast_arrays(x[1:], x[:-1])\r\nValueError: while calling (from <ipython-input-42-6e4db831c1ff>, line 1)\r\n    ak._v2.broadcast_arrays(\r\n        arrays = (<Array [['one', 'two']] type='1 * var * string'>, <Array [[...\r\n        kwargs = {}\r\n    )\r\nError details: cannot broadcast nested list (in compiled code: https://github.com/scikit-hep/awkward-1.0/blob/1.10.0rc1/src/cpu-kernels/awkward_ListArray_broadcast_tooffsets.cpp#L27)\r\n```\r\n\r\nIn this case, broadcasting an Array of strings against an Array of integers produces arrays that have the same structure as though the outer `__array__ = \"string\"` were missing (i.e. broadcasting happens against the underlying characters array):\r\n```python3\r\n>>> import awkward as ak\r\n>>> x = ak._v2.Array([[\"one\", \"two\"], [\"three\", \"four\"]])\r\n>>> y = ak._v2.Array([[1,2],[3, 4]])\r\n>>> ak._v2.broadcast_arrays(x, y)\r\n[<Array [['one', 'two'], ['three', 'four']] type='2 * var * string'>,\r\n <Array [[[1, 1, 1], [2, 2, 2]], [[...], ...]] type='2 * var * var * int64'>]\r\n```\n", "before_files": [{"content": "# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE\n__all__ = (\"broadcast_arrays\",)\nimport awkward as ak\nfrom awkward._backends.dispatch import backend_of\nfrom awkward._backends.numpy import NumpyBackend\nfrom awkward._behavior import behavior_of\nfrom awkward._connect.numpy import unsupported\nfrom awkward._layout import wrap_layout\nfrom awkward._nplikes.numpylike import NumpyMetadata\n\nnp = NumpyMetadata.instance()\ncpu = NumpyBackend.instance()\n\n\ndef broadcast_arrays(\n    *arrays,\n    depth_limit=None,\n    broadcast_parameters_rule=\"one_to_one\",\n    left_broadcast=True,\n    right_broadcast=True,\n    highlevel=True,\n    behavior=None,\n):\n    \"\"\"\n    Args:\n        arrays: Array-like data (anything #ak.to_layout recognizes).\n        depth_limit (None or int, default is None): If None, attempt to fully\n            broadcast the `arrays` to all levels. If an int, limit the number\n            of dimensions that get broadcasted. The minimum value is `1`,\n            for no broadcasting.\n        broadcast_parameters_rule (str): Rule for broadcasting parameters, one of:\n            - `\"intersect\"`\n            - `\"all_or_nothing\"`\n            - `\"one_to_one\"`\n            - `\"none\"`\n        left_broadcast (bool): If True, follow rules for implicit\n            left-broadcasting, as described below.\n        right_broadcast (bool): If True, follow rules for implicit\n            right-broadcasting, as described below.\n        highlevel (bool, default is True): If True, return an #ak.Array;\n            otherwise, return a low-level #ak.contents.Content subclass.\n        behavior (None or dict): Custom #ak.behavior for the output array, if\n            high-level.\n\n    Like NumPy's\n    [broadcast_arrays](https://docs.scipy.org/doc/numpy/reference/generated/numpy.broadcast_arrays.html)\n    function, this function returns the input `arrays` with enough elements\n    duplicated that they can be combined element-by-element.\n\n    For NumPy arrays, this means that scalars are replaced with arrays with\n    the same scalar value repeated at every element of the array, and regular\n    dimensions are created to increase low-dimensional data into\n    high-dimensional data.\n\n    For example,\n\n        >>> ak.broadcast_arrays(5,\n        ...                     [1, 2, 3, 4, 5])\n        [<Array [5, 5, 5, 5, 5] type='5 * int64'>,\n         <Array [1, 2, 3, 4, 5] type='5 * int64'>]\n\n    and\n\n        >>> ak.broadcast_arrays(np.array([1, 2, 3]),\n        ...                     np.array([[0.1, 0.2, 0.3], [10, 20, 30]]))\n        [<Array [[  1,   2,   3], [ 1,  2,  3]] type='2 * 3 * int64'>,\n         <Array [[0.1, 0.2, 0.3], [10, 20, 30]] type='2 * 3 * float64'>]\n\n    Note that in the second example, when the `3 * int64` array is expanded\n    to match the `2 * 3 * float64` array, it is the deepest dimension that\n    is aligned. If we try to match a `2 * int64` with the `2 * 3 * float64`,\n\n        >>> ak.broadcast_arrays(np.array([1, 2]),\n        ...                     np.array([[0.1, 0.2, 0.3], [10, 20, 30]]))\n        ValueError: while calling\n            ak.broadcast_arrays(\n                arrays = (array([1, 2]), array([[ 0.1,  0.2,  0.3],\n               [10. , 20....\n                depth_limit = None\n                broadcast_parameters_rule = 'one_to_one'\n                left_broadcast = True\n                right_broadcast = True\n                highlevel = True\n                behavior = None\n            )\n        Error details: cannot broadcast RegularArray of size 2 with RegularArray of size 3\n\n    NumPy has the same behavior: arrays with different numbers of dimensions\n    are aligned to the right before expansion. One can control this by\n    explicitly adding a new axis (reshape to add a dimension of length 1)\n    where the expansion is supposed to take place because a dimension of\n    length 1 can be expanded like a scalar.\n\n        >>> ak.broadcast_arrays(np.array([1, 2])[:, np.newaxis],\n        ...                     np.array([[0.1, 0.2, 0.3], [10, 20, 30]]))\n        [<Array [[  1,   1,   1], [ 2,  2,  2]] type='2 * 3 * int64'>,\n         <Array [[0.1, 0.2, 0.3], [10, 20, 30]] type='2 * 3 * float64'>]\n\n    Again, NumPy does the same thing (`np.newaxis` is equal to None, so this\n    trick is often shown with None in the slice-tuple). Where the broadcasting\n    happens can be controlled, but numbers of dimensions that don't match are\n    implicitly aligned to the right (fitting innermost structure, not\n    outermost).\n\n    While that might be an arbitrary decision for rectilinear arrays, it is\n    much more natural for implicit broadcasting to align left for tree-like\n    structures. That is, the root of each data structure should agree and\n    leaves may be duplicated to match. For example,\n\n        >>> ak.broadcast_arrays([            100,   200,        300],\n        ...                     [[1.1, 2.2, 3.3],    [], [4.4, 5.5]])\n        [<Array [[100, 100, 100], [], [300, 300]] type='3 * var * int64'>,\n         <Array [[1.1, 2.2, 3.3], [], [4.4, 5.5]] type='3 * var * float64'>]\n\n    One typically wants single-item-per-element data to be duplicated to\n    match multiple-items-per-element data. Operations on the broadcasted\n    arrays like\n\n        one_dimensional + nested_lists\n\n    would then have the same effect as the procedural code\n\n        for x, outer in zip(one_dimensional, nested_lists):\n            output = []\n            for inner in outer:\n                output.append(x + inner)\n            yield output\n\n    where `x` has the same value for each `inner` in the inner loop.\n\n    Awkward Array's broadcasting manages to have it both ways by applying the\n    following rules:\n\n    * If all dimensions are regular (i.e. #ak.types.RegularType), like NumPy,\n      implicit broadcasting aligns to the right, like NumPy.\n    * If any dimension is variable (i.e. #ak.types.ListType), which can\n      never be true of NumPy, implicit broadcasting aligns to the left.\n    * Explicit broadcasting with a length-1 regular dimension always\n      broadcasts, like NumPy.\n\n    Thus, it is important to be aware of the distinction between a dimension\n    that is declared to be regular in the type specification and a dimension\n    that is allowed to be variable (even if it happens to have the same length\n    for all elements). This distinction is can be accessed through the\n    #ak.Array.type, but it is lost when converting an array into JSON or\n    Python objects.\n\n    If arrays have the same depth but different lengths of nested\n    lists, attempting to broadcast them together is a broadcasting error.\n\n        >>> one = ak.Array([[[1, 2, 3], [], [4, 5], [6]], [], [[7, 8]]])\n        >>> two = ak.Array([[[1.1, 2.2], [3.3], [4.4], [5.5]], [], [[6.6]]])\n        >>> ak.broadcast_arrays(one, two)\n        ValueError: while calling\n            ak.broadcast_arrays(\n                arrays = (<Array [[[1, 2, 3], [], [4, ...], [6]], ...] type='3 * var ...\n                depth_limit = None\n                broadcast_parameters_rule = 'one_to_one'\n                left_broadcast = True\n                right_broadcast = True\n                highlevel = True\n                behavior = None\n            )\n        Error details: cannot broadcast nested list\n\n    For this, one can set the `depth_limit` to prevent the operation from\n    attempting to broadcast what can't be broadcasted.\n\n        >>> this, that = ak.broadcast_arrays(one, two, depth_limit=1)\n        >>> this.show()\n        [[[1, 2, 3], [], [4, 5], [6]],\n         [],\n         [[7, 8]]]\n        >>> that.show()\n        [[[1.1, 2.2], [3.3], [4.4], [5.5]],\n         [],\n         [[6.6]]]\n    \"\"\"\n    with ak._errors.OperationErrorContext(\n        \"ak.broadcast_arrays\",\n        {\n            \"arrays\": arrays,\n            \"depth_limit\": depth_limit,\n            \"broadcast_parameters_rule\": broadcast_parameters_rule,\n            \"left_broadcast\": left_broadcast,\n            \"right_broadcast\": right_broadcast,\n            \"highlevel\": highlevel,\n            \"behavior\": behavior,\n        },\n    ):\n        return _impl(\n            arrays,\n            depth_limit,\n            broadcast_parameters_rule,\n            left_broadcast,\n            right_broadcast,\n            highlevel,\n            behavior,\n        )\n\n\ndef _impl(\n    arrays,\n    depth_limit,\n    broadcast_parameters_rule,\n    left_broadcast,\n    right_broadcast,\n    highlevel,\n    behavior,\n):\n    # Need at least one array!\n    if len(arrays) == 0:\n        return []\n\n    backend = backend_of(*arrays, default=cpu)\n\n    inputs = []\n    for x in arrays:\n        y = ak.operations.to_layout(x, allow_record=True, allow_other=True)\n        if not isinstance(y, (ak.contents.Content, ak.Record)):\n            y = ak.contents.NumpyArray(backend.nplike.asarray([y]))\n        inputs.append(y.to_backend(backend))\n\n    def action(inputs, depth, **kwargs):\n        if depth == depth_limit or all(x.is_numpy for x in inputs):\n            return tuple(inputs)\n        else:\n            return None\n\n    behavior = behavior_of(*arrays, behavior=behavior)\n    out = ak._broadcasting.broadcast_and_apply(\n        inputs,\n        action,\n        behavior,\n        left_broadcast=left_broadcast,\n        right_broadcast=right_broadcast,\n        broadcast_parameters_rule=broadcast_parameters_rule,\n        numpy_to_regular=True,\n    )\n    assert isinstance(out, tuple)\n    return [wrap_layout(x, behavior, highlevel) for x in out]\n\n\n@ak._connect.numpy.implements(\"broadcast_arrays\")\ndef _nep_18_impl(*args, subok=unsupported):\n    return broadcast_arrays(*args)\n", "path": "src/awkward/operations/ak_broadcast_arrays.py"}], "after_files": [{"content": "# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE\n__all__ = (\"broadcast_arrays\",)\nimport awkward as ak\nfrom awkward._backends.dispatch import backend_of\nfrom awkward._backends.numpy import NumpyBackend\nfrom awkward._behavior import behavior_of\nfrom awkward._connect.numpy import unsupported\nfrom awkward._layout import wrap_layout\nfrom awkward._nplikes.numpylike import NumpyMetadata\n\nnp = NumpyMetadata.instance()\ncpu = NumpyBackend.instance()\n\n\ndef broadcast_arrays(\n    *arrays,\n    depth_limit=None,\n    broadcast_parameters_rule=\"one_to_one\",\n    left_broadcast=True,\n    right_broadcast=True,\n    highlevel=True,\n    behavior=None,\n):\n    \"\"\"\n    Args:\n        arrays: Array-like data (anything #ak.to_layout recognizes).\n        depth_limit (None or int, default is None): If None, attempt to fully\n            broadcast the `arrays` to all levels. If an int, limit the number\n            of dimensions that get broadcasted. The minimum value is `1`,\n            for no broadcasting.\n        broadcast_parameters_rule (str): Rule for broadcasting parameters, one of:\n            - `\"intersect\"`\n            - `\"all_or_nothing\"`\n            - `\"one_to_one\"`\n            - `\"none\"`\n        left_broadcast (bool): If True, follow rules for implicit\n            left-broadcasting, as described below.\n        right_broadcast (bool): If True, follow rules for implicit\n            right-broadcasting, as described below.\n        highlevel (bool, default is True): If True, return an #ak.Array;\n            otherwise, return a low-level #ak.contents.Content subclass.\n        behavior (None or dict): Custom #ak.behavior for the output array, if\n            high-level.\n\n    Like NumPy's\n    [broadcast_arrays](https://docs.scipy.org/doc/numpy/reference/generated/numpy.broadcast_arrays.html)\n    function, this function returns the input `arrays` with enough elements\n    duplicated that they can be combined element-by-element.\n\n    For NumPy arrays, this means that scalars are replaced with arrays with\n    the same scalar value repeated at every element of the array, and regular\n    dimensions are created to increase low-dimensional data into\n    high-dimensional data.\n\n    For example,\n\n        >>> ak.broadcast_arrays(5,\n        ...                     [1, 2, 3, 4, 5])\n        [<Array [5, 5, 5, 5, 5] type='5 * int64'>,\n         <Array [1, 2, 3, 4, 5] type='5 * int64'>]\n\n    and\n\n        >>> ak.broadcast_arrays(np.array([1, 2, 3]),\n        ...                     np.array([[0.1, 0.2, 0.3], [10, 20, 30]]))\n        [<Array [[  1,   2,   3], [ 1,  2,  3]] type='2 * 3 * int64'>,\n         <Array [[0.1, 0.2, 0.3], [10, 20, 30]] type='2 * 3 * float64'>]\n\n    Note that in the second example, when the `3 * int64` array is expanded\n    to match the `2 * 3 * float64` array, it is the deepest dimension that\n    is aligned. If we try to match a `2 * int64` with the `2 * 3 * float64`,\n\n        >>> ak.broadcast_arrays(np.array([1, 2]),\n        ...                     np.array([[0.1, 0.2, 0.3], [10, 20, 30]]))\n        ValueError: while calling\n            ak.broadcast_arrays(\n                arrays = (array([1, 2]), array([[ 0.1,  0.2,  0.3],\n               [10. , 20....\n                depth_limit = None\n                broadcast_parameters_rule = 'one_to_one'\n                left_broadcast = True\n                right_broadcast = True\n                highlevel = True\n                behavior = None\n            )\n        Error details: cannot broadcast RegularArray of size 2 with RegularArray of size 3\n\n    NumPy has the same behavior: arrays with different numbers of dimensions\n    are aligned to the right before expansion. One can control this by\n    explicitly adding a new axis (reshape to add a dimension of length 1)\n    where the expansion is supposed to take place because a dimension of\n    length 1 can be expanded like a scalar.\n\n        >>> ak.broadcast_arrays(np.array([1, 2])[:, np.newaxis],\n        ...                     np.array([[0.1, 0.2, 0.3], [10, 20, 30]]))\n        [<Array [[  1,   1,   1], [ 2,  2,  2]] type='2 * 3 * int64'>,\n         <Array [[0.1, 0.2, 0.3], [10, 20, 30]] type='2 * 3 * float64'>]\n\n    Again, NumPy does the same thing (`np.newaxis` is equal to None, so this\n    trick is often shown with None in the slice-tuple). Where the broadcasting\n    happens can be controlled, but numbers of dimensions that don't match are\n    implicitly aligned to the right (fitting innermost structure, not\n    outermost).\n\n    While that might be an arbitrary decision for rectilinear arrays, it is\n    much more natural for implicit broadcasting to align left for tree-like\n    structures. That is, the root of each data structure should agree and\n    leaves may be duplicated to match. For example,\n\n        >>> ak.broadcast_arrays([            100,   200,        300],\n        ...                     [[1.1, 2.2, 3.3],    [], [4.4, 5.5]])\n        [<Array [[100, 100, 100], [], [300, 300]] type='3 * var * int64'>,\n         <Array [[1.1, 2.2, 3.3], [], [4.4, 5.5]] type='3 * var * float64'>]\n\n    One typically wants single-item-per-element data to be duplicated to\n    match multiple-items-per-element data. Operations on the broadcasted\n    arrays like\n\n        one_dimensional + nested_lists\n\n    would then have the same effect as the procedural code\n\n        for x, outer in zip(one_dimensional, nested_lists):\n            output = []\n            for inner in outer:\n                output.append(x + inner)\n            yield output\n\n    where `x` has the same value for each `inner` in the inner loop.\n\n    Awkward Array's broadcasting manages to have it both ways by applying the\n    following rules:\n\n    * If all dimensions are regular (i.e. #ak.types.RegularType), like NumPy,\n      implicit broadcasting aligns to the right, like NumPy.\n    * If any dimension is variable (i.e. #ak.types.ListType), which can\n      never be true of NumPy, implicit broadcasting aligns to the left.\n    * Explicit broadcasting with a length-1 regular dimension always\n      broadcasts, like NumPy.\n\n    Thus, it is important to be aware of the distinction between a dimension\n    that is declared to be regular in the type specification and a dimension\n    that is allowed to be variable (even if it happens to have the same length\n    for all elements). This distinction is can be accessed through the\n    #ak.Array.type, but it is lost when converting an array into JSON or\n    Python objects.\n\n    If arrays have the same depth but different lengths of nested\n    lists, attempting to broadcast them together is a broadcasting error.\n\n        >>> one = ak.Array([[[1, 2, 3], [], [4, 5], [6]], [], [[7, 8]]])\n        >>> two = ak.Array([[[1.1, 2.2], [3.3], [4.4], [5.5]], [], [[6.6]]])\n        >>> ak.broadcast_arrays(one, two)\n        ValueError: while calling\n            ak.broadcast_arrays(\n                arrays = (<Array [[[1, 2, 3], [], [4, ...], [6]], ...] type='3 * var ...\n                depth_limit = None\n                broadcast_parameters_rule = 'one_to_one'\n                left_broadcast = True\n                right_broadcast = True\n                highlevel = True\n                behavior = None\n            )\n        Error details: cannot broadcast nested list\n\n    For this, one can set the `depth_limit` to prevent the operation from\n    attempting to broadcast what can't be broadcasted.\n\n        >>> this, that = ak.broadcast_arrays(one, two, depth_limit=1)\n        >>> this.show()\n        [[[1, 2, 3], [], [4, 5], [6]],\n         [],\n         [[7, 8]]]\n        >>> that.show()\n        [[[1.1, 2.2], [3.3], [4.4], [5.5]],\n         [],\n         [[6.6]]]\n    \"\"\"\n    with ak._errors.OperationErrorContext(\n        \"ak.broadcast_arrays\",\n        {\n            \"arrays\": arrays,\n            \"depth_limit\": depth_limit,\n            \"broadcast_parameters_rule\": broadcast_parameters_rule,\n            \"left_broadcast\": left_broadcast,\n            \"right_broadcast\": right_broadcast,\n            \"highlevel\": highlevel,\n            \"behavior\": behavior,\n        },\n    ):\n        return _impl(\n            arrays,\n            depth_limit,\n            broadcast_parameters_rule,\n            left_broadcast,\n            right_broadcast,\n            highlevel,\n            behavior,\n        )\n\n\ndef _impl(\n    arrays,\n    depth_limit,\n    broadcast_parameters_rule,\n    left_broadcast,\n    right_broadcast,\n    highlevel,\n    behavior,\n):\n    # Need at least one array!\n    if len(arrays) == 0:\n        return []\n\n    backend = backend_of(*arrays, default=cpu)\n\n    inputs = []\n    for x in arrays:\n        y = ak.operations.to_layout(x, allow_record=True, allow_other=True)\n        if not isinstance(y, (ak.contents.Content, ak.Record)):\n            y = ak.contents.NumpyArray(backend.nplike.asarray([y]))\n        inputs.append(y.to_backend(backend))\n\n    def action(inputs, depth, **kwargs):\n        # The depth limit is the depth at which we must return, i.e.\n        # the _first_ layout at that depth\n        if depth == depth_limit:\n            return tuple(inputs)\n        # Walk through non-leaf nodes\n        elif all(\n            x.purelist_depth == 1 and not (x.is_option or x.is_indexed) for x in inputs\n        ):\n            return tuple(inputs)\n        else:\n            return None\n\n    behavior = behavior_of(*arrays, behavior=behavior)\n    out = ak._broadcasting.broadcast_and_apply(\n        inputs,\n        action,\n        behavior,\n        left_broadcast=left_broadcast,\n        right_broadcast=right_broadcast,\n        broadcast_parameters_rule=broadcast_parameters_rule,\n        numpy_to_regular=True,\n    )\n    assert isinstance(out, tuple)\n    return [wrap_layout(x, behavior, highlevel) for x in out]\n\n\n@ak._connect.numpy.implements(\"broadcast_arrays\")\ndef _nep_18_impl(*args, subok=unsupported):\n    return broadcast_arrays(*args)\n", "path": "src/awkward/operations/ak_broadcast_arrays.py"}]}