docs: public static Pair<INDArray, INDArray> mergeTimeSeries(INDArray[] arrays, INDArray[] masks) {
          //Merge time series data, and handle masking etc for different length arrays
  
          //Complications with time series:
          //(a) They may have different lengths (if so: need input + output masking arrays)
          //(b) Even if they are all the same length, they may have masking arrays (if so: merge the masking arrays too)
          //(c) Furthermore: mask arrays can be per-time-step (2d) or per output (3d). Per-input masks (3d feature masks)
          //    are not supported, however
  
          long firstLength = arrays[0].size(2);
          long size = arrays[0].size(1);
          long maxLength = firstLength;
  
          boolean hasMask = false;
          int maskRank = -1;
          boolean lengthsDiffer = false;
          int totalExamples = 0;
          for (int i = 0; i < arrays.length; i++) {
              totalExamples += arrays[i].size(0);
              long thisLength = arrays[i].size(2);
              maxLength = Math.max(maxLength, thisLength);
              if (thisLength != firstLength)
                  lengthsDiffer = true;
              if (masks != null && masks[i] != null && masks[i] != null) {
                  maskRank = masks[i].rank();
                  hasMask = true;
              }
  
              if (arrays[i].size(1) != size) {
                  throw new IllegalStateException(
                                  "Cannot merge time series with different size for dimension 1 (first shape: "
                                                  + Arrays.toString(arrays[0].shape()) + ", " + i + "th shape: "
                                                  + Arrays.toString(arrays[i].shape()));
              }
          }
  
          boolean needMask = hasMask || lengthsDiffer;
          INDArray arr = Nd4j.create(arrays[0].dataType(), totalExamples, size, maxLength);
          INDArray mask = (needMask && maskRank != 3 ? Nd4j.ones(arrays[0].dataType(), totalExamples, maxLength) : null);
  
          //Now, merge the time series (and if necessary, mask arrays):
          int examplesSoFar = 0;
          if (!lengthsDiffer && !needMask) {
              //Simplest case: same length, no mask arrays
              for (int i = 0; i < arrays.length; i++) {
                  long thisNExamples = arrays[i].size(0);
                  arr.put(new INDArrayIndex[] {NDArrayIndex.interval(examplesSoFar, examplesSoFar + thisNExamples),
                                  NDArrayIndex.all(), NDArrayIndex.all()}, arrays[i]);
                  examplesSoFar += thisNExamples;
              }
              return new Pair<>(arr, null);
          } else {
              //Either different length, or have mask arrays (or, both)
              if ((lengthsDiffer && !hasMask) || maskRank == 2) {
                  //Standard per-example masking required
                  for (int i = 0; i < arrays.length; i++) {
                      INDArray a = arrays[i];
                      long thisNExamples = a.size(0);
                      long thisLength = a.size(2);
                      arr.put(new INDArrayIndex[] {NDArrayIndex.interval(examplesSoFar, examplesSoFar + thisNExamples),
                                      NDArrayIndex.all(), NDArrayIndex.interval(0, thisLength)}, a);
  
                      if (masks != null && masks[i] != null && masks[i] != null) {
                          INDArray origMask = masks[i];
                          long maskLength = origMask.size(1);
                          mask.put(new INDArrayIndex[] {
                                          NDArrayIndex.interval(examplesSoFar, examplesSoFar + thisNExamples),
                                          NDArrayIndex.interval(0, maskLength)}, origMask);
                          if (maskLength < maxLength) {
                              //Set end mask array to zero...
                              mask.put(new INDArrayIndex[] {
                                              NDArrayIndex.interval(examplesSoFar, examplesSoFar + thisNExamples),
                                              NDArrayIndex.interval(maskLength, maxLength)},
                                              Nd4j.zeros(thisNExamples, maxLength - maskLength));
                          }
                      } else {
                          if (thisLength < maxLength) {
                              //Mask the end
                              mask.put(new INDArrayIndex[] {
                                              NDArrayIndex.interval(examplesSoFar, examplesSoFar + thisNExamples),
                                              NDArrayIndex.interval(thisLength, maxLength)},
                                              Nd4j.zeros(thisNExamples, maxLength - thisLength));
                          }
                      }
  
                      examplesSoFar += thisNExamples;
                  }
              } else if (maskRank == 3) {
                  //Per output masking required. May also be variable length
                  mask = Nd4j.create(arr.dataType(), arr.shape());
                  for (int i = 0; i < arrays.length; i++) {
                      INDArray m = masks[i];
                      INDArray a = arrays[i];
                      long thisNExamples = a.size(0);
                      long thisLength = a.size(2);
                      arr.put(new INDArrayIndex[] {NDArrayIndex.interval(examplesSoFar, examplesSoFar + thisNExamples),
                                      NDArrayIndex.all(), NDArrayIndex.interval(0, thisLength)}, a);
  
                      if (m == null) {
                          //This mask is null -> equivalent to "all present"
                          mask.get(NDArrayIndex.interval(examplesSoFar, examplesSoFar + thisNExamples),
                                          NDArrayIndex.all(), NDArrayIndex.interval(0, thisLength)).assign(1);
                      } else {
                          mask.put(new INDArrayIndex[] {
                                          NDArrayIndex.interval(examplesSoFar, examplesSoFar + thisNExamples),
                                          NDArrayIndex.all(), NDArrayIndex.interval(0, thisLength)}, m);
                      }
  
                      examplesSoFar += thisNExamples;
                  }
              } else {
                  throw new UnsupportedOperationException("Cannot merge time series with mask rank " + maskRank);
              }
          }
  
          return new Pair<>(arr, mask);
      }