Skip to content

Instantly share code, notes, and snippets.

@agoose77
Created March 8, 2022 09:27
Show Gist options
  • Save agoose77/1356db36b66ae8b191eefd112d11ae72 to your computer and use it in GitHub Desktop.
Save agoose77/1356db36b66ae8b191eefd112d11ae72 to your computer and use it in GitHub Desktop.
@ak._connect._numpy.implements("stack")
def stack(arrays, axis=0, merge=True, mergebool=True, highlevel=True, behavior=None):
"""
Args:
arrays: Arrays to concatenate along any dimension.
axis (int): The dimension at which this operation is applied. The
outermost dimension is `0`, followed by `1`, etc., and negative
values count backward from the innermost: `-1` is the innermost
dimension, `-2` is the next level up, etc.
merge (bool): If True, combine data into the same buffers wherever
possible, eliminating unnecessary #ak.layout.UnionArray8_64 types
at the expense of materializing #ak.layout.VirtualArray nodes.
mergebool (bool): If True, boolean and nummeric data can be combined
into the same buffer, losing information about False vs `0` and
True vs `1`; otherwise, they are kept in separate buffers with
distinct types (using an #ak.layout.UnionArray8_64).
highlevel (bool): If True, return an #ak.Array; otherwise, return
a low-level #ak.layout.Content subclass.
behavior (None or dict): Custom #ak.behavior for the output array, if
high-level.
Returns an array with `arrays` concatenated. For `axis=0`, this means that
one whole array follows another. For `axis=1`, it means that the `arrays`
must have the same lengths and nested lists are each concatenated,
element for element, and similarly for deeper levels.
"""
nplike = ak.nplike.of(*arrays)
behavior = ak._util.behaviorof(*arrays, behavior=behavior)
layouts = [
ak.operations.convert.to_layout(
x, allow_record=False if axis == 0 else True, allow_other=True
)
for x in arrays
]
# We need at least one array
contents = [x for x in layouts if isinstance(x, ak.layout.Content)]
if not contents:
raise ValueError(
"need at least one array to concatenate"
+ ak._util.exception_suffix(__file__)
)
# `axis` should lie within the range of possible axes
posaxis = contents[0].axis_wrap_if_negative(axis)
maxdepth = max(x.minmax_depth[1] for x in contents)
if not 0 <= posaxis <= maxdepth:
raise ValueError(
"axis={} is beyond the depth of this array or the depth of this array "
"is ambiguous".format(axis) + ak._util.exception_suffix(__file__)
)
# All arrays should understand the same value for `axis`
if any((x.axis_wrap_if_negative(axis) != posaxis) for x in contents):
raise ValueError(
"arrays to concatenate do not have the same depth for negative "
"axis={}".format(axis) + ak._util.exception_suffix(__file__)
)
# New first axis
if posaxis == 0:
layouts = [
x
if isinstance(x, ak.layout.Content)
else ak.operations.convert.to_layout([x])
for x in layouts
]
length = len(layouts[0])
tags = nplike.repeat(nplike.arange(len(layouts)), length)
index = nplike.broadcast_to(
nplike.arange(length), (len(layouts), length)
).ravel()
inner = ak.layout.UnionArray8_64(
ak.layout.Index8(tags), ak.layout.Index64(index), layouts
).simplify(merge=merge, mergebool=mergebool)
offset = nplike.arange(0, len(index) + 1, length)
out = ak.layout.ListOffsetArray64(ak.layout.Index64(offset), inner)
else:
def stack_contents(contents, length):
nplike = ak.nplike.of(*contents)
tags = nplike.broadcast_to(
nplike.arange(len(contents)), (length, len(contents))
).ravel()
index = nplike.repeat(nplike.arange(length), len(contents))
inner = ak.layout.UnionArray8_64(
ak.layout.Index8(tags), ak.layout.Index64(index), contents
).simplify(merge=merge, mergebool=mergebool)
offset = nplike.arange(0, len(index) + 1, len(contents))
return ak.layout.ListOffsetArray64(ak.layout.Index64(offset), inner)
def getfunction(inputs, depth):
if depth == posaxis and any(
isinstance(x, ak._util.optiontypes) for x in inputs
):
nextinputs = []
for x in inputs:
if isinstance(x, ak._util.optiontypes) and isinstance(
x.content, ak._util.listtypes
):
nextinputs.append(
ak.operations.structure.fill_none(
x, [], axis=0, highlevel=False
)
)
else:
nextinputs.append(x)
inputs = nextinputs
# New axis above existing
if depth == posaxis and all(
isinstance(x, ak._util.listtypes)
or not isinstance(x, ak.layout.Content)
for x in inputs
):
length = max(len(x) for x in inputs if isinstance(x, ak.layout.Content))
nextinputs = []
for x in inputs:
if isinstance(x, ak.layout.Content):
nextinputs.append(x)
else:
nextinputs.append(
ak.layout.ListOffsetArray64(
ak.layout.Index64(
nplike.arange(length + 1, dtype=np.int64)
),
ak.layout.NumpyArray(
nplike.broadcast_to(nplike.array([x]), (length,))
),
)
)
return lambda: (stack_contents(nextinputs, length),)
# New axis at end
elif depth == posaxis and all(
isinstance(x, ak.layout.NumpyArray)
or not isinstance(x, ak.layout.Content)
for x in inputs
):
length = max(len(x) for x in inputs if isinstance(x, ak.layout.Content))
nextinputs = []
for x in inputs:
if isinstance(x, ak.layout.Content):
nextinputs.append(x)
else:
nextinputs.append(
ak.layout.NumpyArray(
nplike.broadcast_to(nplike.array([x]), (length,))
)
)
return lambda: (stack_contents(nextinputs, length),)
elif any(
x.purelist_depth == 1
for x in inputs
if isinstance(x, ak.layout.Content)
):
raise ValueError(
"at least one array is not deep enough to concatenate at "
"axis={}".format(axis) + ak._util.exception_suffix(__file__)
)
else:
return None
(out,) = ak._util.broadcast_and_apply(
layouts,
getfunction,
behavior=behavior,
right_broadcast=False,
numpy_to_regular=True,
)
return ak._util.maybe_wrap(out, behavior, highlevel)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment