/******************************************************************************* * Copyright (c) 2015-2018 Skymind, Inc. * * This program and the accompanying materials are made available under the * terms of the Apache License, Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * * SPDX-License-Identifier: Apache-2.0 ******************************************************************************/ // // @author Yurii Shyrma (iuriish@yahoo.com) // #ifndef LIBND4J_SHAPEUTILS_H #define LIBND4J_SHAPEUTILS_H #include #include namespace nd4j { class ND4J_EXPORT ShapeUtils { public: // evaluate shape for array resulting from tensorDot operation, also evaluate shapes and permutation dimensions for transposition of two input arrays static std::vector evalShapeForTensorDot(const Nd4jLong* aShapeInfo, const Nd4jLong* bShapeInfo, std::vector axesA, std::vector axesB, std::vector& permutAt, std::vector& permutBt, std::vector& shapeAt, std::vector& shapeBt); static std::vector evalShapeForTensorDot(const NDArray* a, const NDArray* b, const std::vector& axesA, const std::vector& axesB, std::vector& permutAt, std::vector& permutBt, std::vector& shapeAt, std::vector& shapeBt); // evaluate resulting shape after reduce operation static Nd4jLong* evalReduceShapeInfo(const char order, std::vector& dimensions, const NDArray& arr, const nd4j::DataType dataType, const bool keepDims = false, const bool supportOldShapes = false, nd4j::memory::Workspace* workspace = nullptr); static Nd4jLong* evalReduceShapeInfo(const char order, std::vector& dimensions, const Nd4jLong* shapeInfo, const nd4j::DataType dataType, const bool keepDims = false, const bool supportOldShapes = false, nd4j::memory::Workspace* workspace = nullptr); static Nd4jLong* evalReduceShapeInfo(const char order, std::vector& dimensions, const NDArray& arr, const bool keepDims = false, const bool supportOldShapes = false, nd4j::memory::Workspace* workspace = nullptr); static Nd4jLong* evalReduceShapeInfo(const char order, std::vector& dimensions, const Nd4jLong* shapeInfo, const bool keepDims = false, const bool supportOldShapes = false, nd4j::memory::Workspace* workspace = nullptr); /** * evaluate output shape for reduce operation when input shape is empty * behavior is analogous to tf */ static Nd4jLong* evalReduceShapeInfoEmpty(const char order, std::vector& dimensions, const Nd4jLong *shapeInfo, const nd4j::DataType dataType, const bool keepDims, nd4j::memory::Workspace* workspace); // evaluate shape for array which is result of repeat operation applied to arr static std::vector evalRepeatShape(int axis, const std::vector& repeats, const NDArray& arr); // evaluate shapeInfo of permuted array static Nd4jLong* evalPermShapeInfo(const int* dimensions, const int rank, const NDArray& arr, nd4j::memory::Workspace* workspace); static Nd4jLong* evalPermShapeInfo(const Nd4jLong* dimensions, const int rank, const NDArray& arr, nd4j::memory::Workspace* workspace); // evaluate shapeInfo of transposed array static Nd4jLong* evalTranspShapeInfo(const NDArray& arr, nd4j::memory::Workspace* workspace); static bool copyVectorPart(std::vector& target, std::vector& source, int rank, int offset); // return new (shorter) sorted dimensions array without dimensions that are present in input vector static std::vector evalDimsToExclude(const int rank, const int dimsLen, const int* dimensions); static std::vector evalDimsToExclude(const int rank, const std::vector& dimensions); // check whether 2 arrays have mutually broadcastable shapes // shape comparison starts from the end static bool areShapesBroadcastable(const NDArray &arr1, const NDArray &arr2); static bool areShapesBroadcastable(Nd4jLong* shapeX, Nd4jLong* shapeY); static bool areShapesBroadcastable(const std::vector& shape1, const std::vector& shape2); // check the possibility of broadcast operation, if true then return shapeInfo of resulting array // if evalMinMax == false then array with larger rank has to be passed as first argument static bool evalBroadcastShapeInfo(const NDArray& max, const NDArray& min, const bool evalMinMax, Nd4jLong*& resultShapeInfo, nd4j::memory::Workspace* workspace); static bool evalBroadcastShapeInfo(Nd4jLong *max, Nd4jLong *min, const bool evalMinMax, Nd4jLong*& resultShapeInfo, nd4j::memory::Workspace* workspace); // evaluate sorted vector of max axes to create tads along in case of simple broadcast operation // if simple broadcast is not possible then empty vector is returned // PLEASE NOTE: condition (rank_max >= rank_min) should be satisfied ! static std::vector tadAxesForSimpleBroadcast(const NDArray& max, const NDArray& min); // check the possibility of broadcast operation for set of arrays, if true then return resulting broadcasted shapeInfo static bool evalCommonBroadcastShapeInfo(const std::vector& arrays, Nd4jLong*& resultShapeInfo, memory::Workspace* workspace = nullptr); // return sorted vector of dimensions common (same) for two arrays, dimensions values corresponds to array with bigger rank // for example if arr1{2,7}, arr2{2,5,4,7} then vector = {0,3} static std::vector getDimsWithSameShape(const NDArray& max, const NDArray& min); // evaluate shapeInfo for resulting array of tile operation static Nd4jLong* evalTileShapeInfo(const NDArray& arr, const std::vector& reps, nd4j::memory::Workspace* workspace); // returns shape part of shapeInfo as std::vector static std::vector pullShapeFromShapeInfo(Nd4jLong *shapeInfo); static std::string shapeAsString(const NDArray* array); static std::string shapeAsString(const std::vector& shape); static std::string shapeAsString(const Nd4jLong* shapeInfo); static std::string shapeAsString(const int rank, const Nd4jLong* shapeInfo); static std::string strideAsString(const NDArray* array); static std::vector shapeAsVector(const Nd4jLong* shapeInfo); // evaluate shapeInfo for diagonal array which is made using input arr elements as diagonal static Nd4jLong* evalDiagShapeInfo(const Nd4jLong* shapeInfo, nd4j::memory::Workspace* workspace); static std::vector evalBroadcastBackwardAxis(const Nd4jLong *operand, const Nd4jLong *result); // utility to calculate matrix product shape with give source shapes and additional params // returns ShapeList pointer with result shape static Nd4jLong* matrixProductShape(Nd4jLong* theFirstShape, Nd4jLong* theSecondShape, bool shouldTranspondFirst, bool shouldTranspondSecond, nd4j::DataType dtype, nd4j::memory::Workspace* workspace); /** * This method evaluates permutation vector necessary for reducing of shapeFrom to shapeTo * if shapeFrom is identical to shapeTo (permutation is unnecessary) then empty vector is returned * in case of permutation is impossible an exception is thrown */ static std::vector evalPermutFromTo(const std::vector& shapeFrom, const std::vector& shapeTo); /** * This method composes shape (shape only, not whole shapeInfo!) using dimensions values and corresponding indexes, * please note: the size of input vector dimsAndIdx must always be even, since the numbers of dimensions and indexes are the same, * for example if dimsAndIdx = {dimC,dimB,dimA, 2,1,0} then output vector = {dimA,dimB,dimC} */ static std::vector composeShapeUsingDimsAndIdx(const std::vector& dimsAndIdx); /** * x * y = c, evaluate shape for array resulting from mmul operation * possible cases: dot product (xRank=yRank=1), matrix-vector product (xRank=2, yRank=1), vector-matrix product (xRank=1, yRank=2), matrix-matrix product (xRank=yRank and rank >=2) */ static std::vector evalShapeForMatmul(const Nd4jLong* xShapeInfo, const Nd4jLong* yShapeInfo, const bool transX, const bool transY); /** * evaluate number of sub-arrays along dimensions stored in dimsToExclude * i.e. if shape is [2,3,4,5] and dimsToExclude={0,2}, then number of sub-arrays = 8 */ static Nd4jLong getNumOfSubArrs(const Nd4jLong* shapeInfo, const std::vector& dimsToExclude); /** * evaluate indexes ranges that define sub-array of array having shape=shapeInfo * subArrIdx - index of current sub-array * shapeInfo - shapeInfo of array for which to evaluate sub-arrays * dimsToExclude - MUST BE SORTED, dimensions to evaluate sub-arrays along, i.e. when shape is [2,3,4,5] and dimsToExclude={0,2}, then there will be 8 sub-arrays with shape [3,5], * if dimsToExclude is empty then idxRanges containing all zeros (means whole array) will be returned. * idxRanges - where to put result, the length of idxRanges must be equal to 2*shapeInfo[0] */ static void evalIdxRangesForSubArr(const Nd4jLong subArrIdx, const Nd4jLong* shapeInfo, const std::vector& dimsToExclude, Nd4jLong* idxRanges); /** * return shape without unities, for example if shape is [1,2,1,3] then [2,3] will be returned * if unities are not present in given shapeInfo then exactly identical shape will be returned, for example [2,3] -> [2,3] * edge case: if given shape is [1,1,1,...,1] (all dims are unities) then output will be empty and means scalar */ static std::vector evalDimsWithoutUnities(const Nd4jLong* shapeInfo); /** * method returns false if permut == {0,1,2,...permut.size()-1} - in that case permutation is unnecessary */ FORCEINLINE static bool isPermutNecessary(const std::vector& permut); /** * calculates strides using "dest" shape and given "order", also copies data type from "source" to "dest" */ static void updateStridesAndType(Nd4jLong* dest, const Nd4jLong* source, const char order); /** * calculates strides using "dest" shape and "order", also set "dtype" into "dest" */ static void updateStridesAndType(Nd4jLong* dest, const DataType dtype, const char order); /** * This method retuns number of bytes required for string tensor * @param numStrings * @return */ static FORCEINLINE Nd4jLong stringBufferHeaderRequirements(Nd4jLong numStrings) { // we store +1 offset return (numStrings + 1) * sizeof(Nd4jLong); } /* * check whether arr1/arr2 is sub-array of arr2/arr1, * this method do not evaluate what array is sub-array, it returns true if arr1 is sub-array of arr2 or arr2 is sub-array of arr1 * sameDims is filled (and sorted) with dimensions values that match both in arr1 and arr2 shapes (unities are ignored) * for example: * if arr1{2,3} and arr2{2,4,3,7} then return true and sameDims contains {0,2} * if arr1{1,1,3,1,3,1,1} and arr2{1,2,3,1,3} then return true and sameDims contains {2,4} * if arr1{2,1,4,1,7,5} and arr2{1,1,4,5} then return true and sameDims contains {2,5} static bool isSubArrayCase(const NDArray& arr1, const NDArray& arr2, std::vector& sameDims); */ }; ////////////////////////////////////////////////////////////////////////// ///// IMLEMENTATION OF INLINE METHODS ///// ////////////////////////////////////////////////////////////////////////// FORCEINLINE bool ShapeUtils::isPermutNecessary(const std::vector& permut) { for(int i=0; i