text: // Copyright (C) 2018-2019 Intel Corporation
  // SPDX-License-Identifier: Apache-2.0
  //
  
  /**
   * @brief a header file for internal Layers structure to describe layers information
   * @file ie_layers.h
   */
  #pragma once
  
  #include <memory>
  #include <string>
  #include <vector>
  #include <algorithm>
  #include <map>
  #include <iterator>
  #include <limits>
  #include <cctype>
  #include "ie_common.h"
  #include "ie_data.h"
  #include "ie_blob.h"
  #include "ie_device.hpp"
  #include "ie_layers_property.hpp"
  
  namespace InferenceEngine {
  /**
   * @brief This is an internal common Layer parameter parsing arguments
   */
  struct LayerParams {
      /// @brief Layer name
      std::string name;
      /// @brief Layer type
      std::string type;
      /// @brief Layer precision
      Precision precision;
  };
  
  /**
   * @brief This is a base abstraction Layer - all DNN Layers inherit from this class
   */
  class CNNLayer  {
  public:
      /**
       * @brief A shared pointer to CNNLayer
       */
      using  Ptr = std::shared_ptr<CNNLayer>;
  
      /**
       * @brief Layer name
       */
      std::string name;
      /**
       * @brief Layer type
       */
      std::string type;
      /**
       * @brief Layer base operating precision
       */
      Precision precision;
      /**
       * @brief A vector of pointers to the output data elements of this layer in the di-graph (order matters)
       */
      std::vector<DataPtr> outData;
      /**
       * @brief A vector of weak pointers to the input data elements of this layer in the di-graph (order matters)
       */
      std::vector<DataWeakPtr> insData;
      /**
       * @brief If suggested to fuse - a pointer to the layer which needs to be fused with this layer
       */
      Ptr _fusedWith;
      /**
       * @brief Convenience user values to store in this object as extra data
       */
      UserValue userValue;
      /**
       * @brief Layer affinity set by user.
       */
      std::string affinity;
  
      /**
       * @brief A constructor. Creates a new CNNLayer instance and initializes layer parameters with the given values.
       * @param prms Basic common parsing parameters
       */
      explicit CNNLayer(const LayerParams &prms) : name(prms.name), type(prms.type),
                                                   precision(prms.precision), userValue({0}) {
      }
  
      /**
       * @brief A virtual destructor
       */
      virtual ~CNNLayer() = default;
  
      /**
       * @brief Sets a layer to be fused with
       * @param layer Reference to the layer to be fused with
       */
      void fuse(Ptr &layer) {
          _fusedWith = layer;
      }
  
      /**
       * @brief Returns the first element of the input data for this layer
       * @return A smart pointer to the input data element
       */
      virtual const DataPtr input() const {
          if (insData.empty()) {
              THROW_IE_EXCEPTION << "Internal error: input data is empty";
          }
          auto lockedFirstInsData = insData[0].lock();
          if (!lockedFirstInsData) {
              THROW_IE_EXCEPTION << "Internal error: unable to lock weak_ptr\n";
          }
          return lockedFirstInsData;
      }
  
      /**
       * @brief Checks if the input data and layer data are legitimate
       */
      INFERENCE_ENGINE_API_CPP(void) validateLayer();
  
      /**
       * @brief Parse string with float in accordance with IE rules
       * @param str input string with float value
       * @return float value if parsing was successful
       * @throws InferenceEngineException in case of parsing error
       */
      static float ie_parse_float(const std::string &str) {
          if (str == "-inf") {
              return -std::numeric_limits<float>::infinity();
          } else if (str == "inf") {
              return std::numeric_limits<float>::infinity();
          } else {
              float res;
              std::stringstream val_stream(str);
              val_stream.imbue(std::locale("C"));
              val_stream >> res;
              if (!val_stream.eof()) THROW_IE_EXCEPTION;
              return res;
          }
      }
  
      /**
       * @brief Gets float value for the given parameter
       * @param param name of the parameter to find
       * @param def default value of the parameter if not found
       * @return float value
       */
      float GetParamAsFloat(const char* param, float def) const {
          std::string val = GetParamAsString(param, std::to_string(def).c_str());
          try {
              return ie_parse_float(val);
          } catch (...) {
              THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name
                                 << ". Value " << val << " cannot be casted to float.";
          }
      }
  
      /**
       * @brief Returns a float value for the given layer parameter
       * @param param Name of the layer parameter
       * @return A float value for the specified parameter
       */
      float GetParamAsFloat(const char *param) const {
          std::string val = GetParamAsString(param);
          try {
              return ie_parse_float(val);
          } catch (...) {
              THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name
                                 << ". Value " << val << " cannot be casted to float.";
          }
      }
  
      /**
       * @brief Returns a vector of float values for the given parameter or returns the default value
       * @param param Name of the layer parameter
       * @param def Default value of the parameter if not found
       * @return vector of float values
       */
      std::vector<float> GetParamAsFloats(const char *param, std::vector<float> def) const {
          std::string vals = GetParamAsString(param, "");
          std::vector<float> result;
          std::istringstream stream(vals);
          std::string str;
          if (vals.empty())
              return def;
          while (getline(stream, str, ',')) {
              try {
                  float val = ie_parse_float(str);
                  result.push_back(val);
              } catch (...) {
                  THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " " << str << " from IR for layer " << name
                                     << ". Value " << vals << " cannot be casted to floats.";
              }
          }
          return result;
      }
  
      /**
       * @brief Returns a vector of float values for the given parameter
       * @param param Name of the layer parameter
       * @return vector of float values
       */
      std::vector<float> GetParamAsFloats(const char *param) const {
          std::string vals = GetParamAsString(param);
          std::vector<float> result;
          std::istringstream stream(vals);
          std::string str;
          while (getline(stream, str, ',')) {
              try {
                  float val = ie_parse_float(str);
                  result.push_back(val);
              } catch (...) {
                  THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " " << str << " from IR for layer " << name
                                     << ". Value " << vals << " cannot be casted to floats.";
              }
          }
          return result;
      }
  
      /**
       * @brief Returns an integer value for the given parameter or returns the default value
       * @param param Name of the layer parameter
       * @param def Default value of the parameter if not found
       * @return An int value for the specified parameter
       */
      int GetParamAsInt(const char *param, int def) const {
          std::string val = GetParamAsString(param, std::to_string(def).c_str());
          try {
              return std::stoi(val);
          } catch (...) {
              THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name
                                 << ". Value " << val << " cannot be casted to int.";
          }
      }
  
      /**
       * @brief Returns an integer value for the given parameter
       * @param param Name of the layer parameter
       * @return An int value for the specified parameter
       */
      int GetParamAsInt(const char *param) const {
          std::string val = GetParamAsString(param);
          try {
              return std::stoi(val);
          } catch (...) {
              THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name
                                 << ". Value " << val << " cannot be casted to int.";
          }
      }
  
  
      /**
       * @brief Returns a vector of int values for the given parameter or returns the default value
       * @param param Name of the layer parameter
       * @param def Default value of the parameter if not found
       * @return vector of int values
       */
      std::vector<int> GetParamAsInts(const char *param, std::vector<int> def) const {
          std::string vals = GetParamAsString(param, "");
          std::vector<int> result;
          std::istringstream stream(vals);
          std::string str;
          if (vals.empty())
              return def;
          while (getline(stream, str, ',')) {
              try {
                  result.push_back(std::stoi(str));
              } catch (...) {
                  THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " " << str << " from IR for layer " << name
                                     << ". Value " << vals << " cannot be casted to int.";
              }
          }
          return result;
      }
  
      /**
       * @brief Returns a vector of int values for the given parameter
       * @param param Name of the layer parameter
       * @return vector of int values
       */
      std::vector<int> GetParamAsInts(const char *param) const {
          std::string vals = GetParamAsString(param);
          std::vector<int> result;
          std::istringstream stream(vals);
          std::string str;
          while (getline(stream, str, ',')) {
              try {
                  result.push_back(std::stoi(str));
              } catch (...) {
                  THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " " << str << " from IR for layer " << name
                          << ". Value " << vals <<  " cannot be casted to int.";
              }
          }
          return result;
      }
      /**
       * @brief Returns an unsigned integer value for the given parameter or returns the default value
       * @param param Name of the layer parameter
       * @param def Default value of the parameter if not found
       * @return An unsigned integer value for the specified parameter
       */
      unsigned int GetParamAsUInt(const char *param, unsigned int def) const {
          std::string val = GetParamAsString(param, std::to_string(def).c_str());
          std::string message = "Cannot parse parameter " + std::string(param) + " from IR for layer " + name
                                + ". Value " + val + " cannot be casted to int.";
          try {
              int value = std::stoi(val);
              if (value < 0) {
                  THROW_IE_EXCEPTION << message;
              }
              return static_cast<unsigned int>(value);
          } catch (...) {
              THROW_IE_EXCEPTION << message;
          }
      }
  
      /**
       * @brief Returns an unsigned integer value for the given parameter
       * @param param Name of the layer parameter
       * @return An unsigned integer value for the specified parameter
       */
      unsigned int GetParamAsUInt(const char *param) const {
          std::string val = GetParamAsString(param);
          std::string message = "Cannot parse parameter " + std::string(param) + " from IR for layer " + name
                                                   + ". Value " + val + " cannot be casted to unsigned int.";
          try {
              int value = std::stoi(val);
              if (value < 0) {
                  THROW_IE_EXCEPTION << message;
              }
              return static_cast<unsigned int>(value);
          } catch (...) {
              THROW_IE_EXCEPTION << message;
          }
      }
  
  
      /**
       * @brief Returns a vector of unsigned int values for the given parameter or returns the default value
       * @param param Name of the layer parameter
       * @param def Default value of the parameter if not found
       * @return vector of unsigned int values
       */
      std::vector<unsigned int> GetParamAsUInts(const char *param, std::vector<unsigned int> def) const {
          std::string vals = GetParamAsString(param, "");
          std::vector<unsigned int> result;
          std::istringstream stream(vals);
          std::string str;
          std::string message = "Cannot parse parameter " + std::string(param) + " " + str + " from IR for layer " + name
                                + ". Value " + vals +  " cannot be casted to unsigned int.";
          if (vals.empty())
              return def;
          while (getline(stream, str, ',')) {
              try {
                  int value = std::stoi(str);
                  if (value < 0) {
                      THROW_IE_EXCEPTION << message;
                  }
                  result.push_back(static_cast<unsigned int>(value));
              } catch (...) {
                  THROW_IE_EXCEPTION << message;
              }
          }
          return result;
      }
  
      /**
       * @brief Returns a vector of unsigned int values for the given parameter
       * @param param Name of the layer parameter
       * @return vector of unsigned int values
       */
      std::vector<unsigned int> GetParamAsUInts(const char *param) const {
          std::string vals = GetParamAsString(param);
          std::vector<unsigned int> result;
          std::istringstream stream(vals);
          std::string str;
          std::string message = "Cannot parse parameter " + std::string(param) + " " + str + " from IR for layer " + name
                                                          + ". Value " + vals +  " cannot be casted to int.";
          while (getline(stream, str, ',')) {
              try {
                  int value = std::stoi(str);
                  if (value < 0) {
                      THROW_IE_EXCEPTION << message;
                  }
                  result.push_back(static_cast<unsigned int>(value));
              } catch (...) {
                  THROW_IE_EXCEPTION << message;
              }
          }
          return result;
      }
      /**
       * @brief Returns an boolean value for the given parameter.
       * The valid values are (true, false, 1, 0).
       * @param param Name of the layer parameter
       * @param def Default value of the parameter if not found
       * @return An bool value for the specified parameter
       */
      bool GetParamAsBool(const char *param, bool def) const {
          std::string val = GetParamAsString(param, std::to_string(def).c_str());
          std::string loweredCaseValue;
          std::transform(val.begin(), val.end(), std::back_inserter(loweredCaseValue), [](char value) {
              return std::tolower(value);
          });
  
          bool result = false;
  
          if (!(std::istringstream(loweredCaseValue) >> std::boolalpha >> result)) {
              // attempting parse using non alpha bool
              return (GetParamAsInt(param, def) != 0);
          }
  
          return result;
      }
      /**
       * @deprecated Use CNNLayer::GetParamAsBool
       */
      INFERENCE_ENGINE_DEPRECATED
      bool GetParamsAsBool(const char *param, bool def) const {
          return GetParamAsBool(param, def);
      }
  
      /**
       * @brief Returns a string value for the given parameter or returns the default one
       * @param param Name of the layer parameter
       * @param def Default value of the parameter if not found
       * @return A string value
       */
      std::string GetParamAsString(const char *param, const char *def) const {
          auto it = params.find(param);
          if (it == params.end() || it->second.empty()) {
              return def;
          }
          return (*it).second;
      }
  
      /**
      * @brief Checks the param presence in the layer
      * @param param Name of the layer parameter
      * @return a bool depending param presence
      */
      bool CheckParamPresence(const char *param) const {
          auto it = params.find(param);
          if (it == params.end()) {
              return false;
          }
          return true;
      }
  
      /**
       * @brief Returns a string value for the given parameter.
       * Throws exception if parameter was not found.
       * @param param Name of the layer parameter
       * @return A string value
       */
      std::string GetParamAsString(const char *param) const {
          auto it = params.find(param);
          if (it == params.end()) {
              THROW_IE_EXCEPTION << "No such parameter name '" << param << "' for layer " << name;
          }
          return (*it).second;
      }
  
      std::vector<std::string> GetParamAsStrings(const char *param, std::vector<std::string> def) const {
          std::string vals = GetParamAsString(param, "");
          std::vector<std::string> result;
          std::istringstream stream(vals);
          std::string str;
          if (vals.empty())
              return def;
          while (getline(stream, str, ',')) {
              try {
                  result.push_back(str);
              } catch (...) {
                  THROW_IE_EXCEPTION << "Cannot parse parameter " << param << " from IR for layer " << name << ".";
              }
          }
          return result;
      }
  
      /**
       * @brief Map of pairs: (parameter name, parameter value)
       */
      std::map<std::string, std::string> params;
  
      /**
       * @brief Map of pairs: (name, weights/biases blob)
       */
      std::map<std::string, Blob::Ptr> blobs;
  };
  
  /**
   * @brief Alias for CNNLayer object
   */
  using GenericLayer = class CNNLayer;
  
  /**
   * @brief This class represents a layer with Weights and/or Biases (e.g. Convolution/Fully Connected, etc.)
   */
  class WeightableLayer : public CNNLayer {
  public:
      /**
       * @brief A default constructor. Constructs a WeightableLayer instance and initiates layer parameters with the given values
       * @param prms Initial layer parameters
       */
      explicit WeightableLayer(const LayerParams &prms) : CNNLayer(prms) {}
  
      /**
       * @brief A pointer to a weights blob
       */
      Blob::Ptr _weights;
      /**
       * @brief A pointer to a biases blob
       */
      Blob::Ptr _biases;
  
      /**
       * @brief Constructs a WeightableLayer instance and initiates layer parameters with the given values
       */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief convinenent way to declare property with backward compatibility to 2D members
   */
  #define DEFINE_PROP(prop_name) \
  PropertyVector<unsigned int> prop_name;\
  unsigned int &prop_name##_x = prop_name.at(X_AXIS);\
  unsigned int &prop_name##_y = prop_name.at(Y_AXIS);\
  
  /**
   * @brief This class represents a standard 3D Convolution Layer
   */
  class ConvolutionLayer : public WeightableLayer {
  public:
      /**
       * @brief A convolution kernel array [X, Y, Z, ...]
       */
      DEFINE_PROP(_kernel);
      /**
       * @brief A convolution paddings begin array [X, Y, Z, ...]
       */
      DEFINE_PROP(_padding);
      /**
       * @brief A convolution paddings end array [X, Y, Z, ...]
       */
      PropertyVector<unsigned int> _pads_end;
      /**
       * @brief A convolution strides array [X, Y, Z, ...]
       */
      DEFINE_PROP(_stride);
      /**
       * @brief A convolution dilations array [X, Y, Z, ...]
       */
      DEFINE_PROP(_dilation);
      /**
       * @brief A number of output feature maps (size) generating the 3'rd output dimension
       */
      unsigned int _out_depth = 0u;
      /**
       * @brief Number of groups
       */
      unsigned int _group = 1u;
      /**
       * @brief Auto padding type
       */
      std::string _auto_pad;
  
      /**
       * @brief Creates a new ConvolutionLayer instance.
       */
      explicit ConvolutionLayer(const LayerParams &p) : WeightableLayer(p),
              _kernel(2, 0u), _padding(2, 0u), _stride(2, 1u), _dilation(2, 1u) {}
      /**
       * @brief assignment operator
       */
      ConvolutionLayer & operator = (const ConvolutionLayer & that) {
          if (&that != this) {
              WeightableLayer::operator=(that);
              _kernel = that._kernel;
              _padding = that._padding;
              _pads_end = that._pads_end;
              _stride = that._stride;
              _dilation = that._dilation;
              _out_depth = that._out_depth;
              _group = that._group;
          }
          return *this;
      }
      /**
       * @brief move assignment operator
       */
      ConvolutionLayer& operator = (ConvolutionLayer &&) = default;
      /**
       * @brief copy constructor
       */
      ConvolutionLayer(const ConvolutionLayer & that) : WeightableLayer(that) {
          operator = (that);
      }
      /**
       * @brief move constructor
       */
      ConvolutionLayer(ConvolutionLayer &&) = default;
  };
  
  /**
   * @brief This class represents a standard deconvolution layer
   */
  class DeconvolutionLayer : public ConvolutionLayer {
   public:
      using ConvolutionLayer::ConvolutionLayer;
      using ConvolutionLayer::operator=;
  };
  
  /**
   * @brief This class represents a standard deformable convolution layer
   */
  class DeformableConvolutionLayer : public ConvolutionLayer {
  public:
      using ConvolutionLayer::ConvolutionLayer;
      using ConvolutionLayer::operator=;
  
      /**
       * @brief Number of deformable groups
       */
      unsigned int _deformable_group = 1u;
  };
  
  /**
   * @brief This class represents a standard pooling layer
   */
  class PoolingLayer : public CNNLayer {
  public:
      /**
       * @brief Pooling kernel array [X, Y, Z, ...]
       */
      DEFINE_PROP(_kernel);
      /**
       * @brief Pooling paddings begin array [X, Y, Z, ...]
       */
      DEFINE_PROP(_padding);
      /**
       * @brief Pooling paddings end array [X, Y, Z, ...]
       */
      PropertyVector<unsigned int> _pads_end;
      /**
       * @brief Pooling strides array [X, Y, Z, ...]
       */
      DEFINE_PROP(_stride);
  
      /**
       * @enum PoolType
       * @brief Defines available pooling types
       */
      enum PoolType {
          MAX = 1,
          AVG = 2,
          STOCH = 3,
          ROI = 4,
          SPACIAL_PYRAMID = 5
      };
  
      /**
       * @brief A pooling type
       */
      PoolType _type = MAX;
  
      /**
       * @brief A flag that indicates if padding is excluded or not
       */
      bool _exclude_pad = false;
      /**
       * @brief Auto padding type
       */
      std::string _auto_pad;
  
      /**
      * @brief Creates a new PoolingLayer instance.
      */
      explicit PoolingLayer(const LayerParams &p) : CNNLayer(p),
              _kernel(2, 0u), _padding(2, 0u), _stride(2, 0u) {}
  
      /**
       * @brief assignment operator
       */
      PoolingLayer & operator = (const PoolingLayer & that) {
          if (&that != this) {
              CNNLayer::operator=(that);
              _kernel = that._kernel;
              _padding = that._padding;
              _pads_end = that._pads_end;
              _stride = that._stride;
              _type = that._type;
              _exclude_pad = that._exclude_pad;
          }
          return *this;
      }
      /**
       * @brief move assignment operator
       */
      PoolingLayer& operator = (PoolingLayer &&) = default;
  
      /**
       * @brief copy constructor
       */
      PoolingLayer(const PoolingLayer & that) : CNNLayer(that) {
          operator=(that);
      }
  
      /**
       * @brief move constructor
       */
      PoolingLayer(PoolingLayer &&) = default;
  };
  
  /**
   * @brief This class represents a standard binary convolution layer
   */
  class BinaryConvolutionLayer : public WeightableLayer {
  public:
      /**
      * @enum eBinaryConvolutionMode
      * @brief Defines possible modes of binary convolution operation
      */
      enum eBinaryConvolutionMode {
          xnor_popcount = 0
      };
  
      /**
      * @brief Mode of binary convolution operation
      */
      eBinaryConvolutionMode _mode = xnor_popcount;
  
      /**
       * @brief A number of input feature maps (size) generating the 3'rd input dimension
       */
      unsigned int _in_depth = 0u;
  
      /**
      * @brief A pad value which is used to fill pad area
      */
      float _pad_value = 0.0f;
  
      /**
       * @brief A convolution kernel array [X, Y, Z, ...]
       */
      DEFINE_PROP(_kernel);
      /**
       * @brief A convolution paddings begin array [X, Y, Z, ...]
       */
      DEFINE_PROP(_padding);
      /**
       * @brief A convolution paddings end array [X, Y, Z, ...]
       */
      PropertyVector<unsigned int> _pads_end;
      /**
       * @brief A convolution strides array [X, Y, Z, ...]
       */
      DEFINE_PROP(_stride);
      /**
       * @brief A convolution dilations array [X, Y, Z, ...]
       */
      DEFINE_PROP(_dilation);
      /**
       * @brief A number of output feature maps (size) generating the 3'rd output dimension
       */
      unsigned int _out_depth = 0u;
      /**
       * @brief Number of groups
       */
      unsigned int _group = 1u;
      /**
       * @brief Auto padding type
       */
      std::string _auto_pad;
  
      /**
       * @brief Creates a new BinaryConvolutionLayer instance.
       */
      explicit BinaryConvolutionLayer(const LayerParams &p) : WeightableLayer(p),
              _kernel(2, 0u), _padding(2, 0u), _stride(2, 1u), _dilation(2, 1u) {}
      /**
       * @brief assignment operator
       */
      BinaryConvolutionLayer & operator = (const BinaryConvolutionLayer & that) {
          if (&that != this) {
              WeightableLayer::operator=(that);
              _kernel = that._kernel;
              _padding = that._padding;
              _pads_end = that._pads_end;
              _stride = that._stride;
              _dilation = that._dilation;
              _out_depth = that._out_depth;
              _group = that._group;
              _mode = that._mode;
              _in_depth = that._in_depth;
              _pad_value = that._pad_value;
          }
          return *this;
      }
      /**
       * @brief move assignment operator
       */
      BinaryConvolutionLayer& operator = (BinaryConvolutionLayer &&) = default;
      /**
       * @brief copy constructor
       */
      BinaryConvolutionLayer(const BinaryConvolutionLayer & that) : WeightableLayer(that) {
          operator = (that);
      }
      /**
       * @brief move constructor
       */
      BinaryConvolutionLayer(BinaryConvolutionLayer &&) = default;
  };
  
  #undef DEFINE_PROP
  
  /**
   * @brief This class represents a fully connected layer
   */
  class FullyConnectedLayer : public WeightableLayer {
  public:
      /**
       * @brief A size of output
       */
      unsigned int _out_num = 0;
  
      /**
      * @brief Creates a new FullyConnectedLayer instance and initializes layer parameters with the given values.
      */
      using WeightableLayer::WeightableLayer;
  };
  
  /**
   * @brief This class represents concatenation layer
   * Takes as input several data elements and merges them to one using the supplied axis
   */
  class ConcatLayer : public CNNLayer {
  public:
      /**
       * @brief An axis on which concatenation operation is performed
       */
      unsigned int _axis = 1;
  
      /**
      * @brief Creates a new ConcatLayer instance and initializes layer parameters with the given values.
      * If batch is used, then batch needs to be specified as an input dimension also
      * In current implementation 1 means channels, 0 - batch
      */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a layer that evenly splits the input into the supplied outputs
   */
  class SplitLayer : public CNNLayer {
  public:
      /**
       * @brief An axis on which split operation is performed
       */
      unsigned int _axis = 1;
  
      /**
      * @brief Creates a new SplitLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a Linear Response Normalization (LRN) Layer
   */
  class NormLayer : public CNNLayer {
  public:
      /**
       * @brief Response size
       */
      unsigned int _size = 0;
      /**
       * @brief K
       */
      unsigned int _k = 1;
      /**
       * @brief Alpha coefficient
       */
      float _alpha = 0;
      /**
       * @brief Beta coefficient
       */
      float _beta = 0;
      /**
       * @brief Flag to specify normalization across feature maps (true) or across channels
       */
      bool _isAcrossMaps = false;
  
      /**
       * @brief Creates a new NormLayer instance.
       */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents standard softmax Layer
   */
  class SoftMaxLayer : public CNNLayer {
  public:
      /**
       * @brief Axis number for a softmax operation
       */
      int axis = 1;
      /**
       * @brief Creates a new SoftMaxLayer instance.
       */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @class GRNLayer
   * @brief This class represents standard GRN Layer
   */
  class GRNLayer : public CNNLayer {
  public:
      /**
      * @brief A default constructor. Creates a new GRNLayer instance and initializes layer parameters with the given values.
      * @param prms Initial layer parameters
      */
      explicit GRNLayer(const LayerParams &prms) : CNNLayer(prms), bias(0.f) {}
  
      /**
       * @brief Bias for squares sum
       */
      float bias = 0.f;
  };
  
  /**
   * @class MVNLayer
   * @brief This class represents standard MVN Layer
   */
  class MVNLayer : public CNNLayer {
  public:
      /**
      * @brief A default constructor. Creates a new MVNLayer instance and initializes layer parameters with the given values.
      * @param prms Initial layer parameters
      */
      explicit MVNLayer(const LayerParams &prms) : CNNLayer(prms), across_channels(0), normalize(1) {}
  
      /**
       * @brief Indicate that mean value is calculated across channels
       */
      int across_channels;
  
      /**
      * @brief Indicate that the result needs to be normalized
      */
      int normalize = 1;
  };
  
  /**
   * @brief This class represents a Rectified Linear activation layer
   */
  class ReLULayer : public CNNLayer {
  public:
      /**
       * @brief Negative slope is used to takle negative inputs instead of setting them to 0
       */
      float negative_slope = 0.0f;
  
      /**
       * @brief Creates a new ReLULayer instance.
       */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a Clamp activation layer
   * Clamps all tensor elements into the range [min_value, max_value]
   */
  class ClampLayer : public CNNLayer {
  public:
      /**
       * @brief A minimum value
       */
      float min_value = 0.0f;
  
      /**
       * @brief A maximum value
       */
      float max_value = 1.0f;
      /**
       * @brief Creates a new ClampLayer instance.
       */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
   * @brief This class represents a ReLU6 activation layer
   * Clamps all tensor elements into the range [0, 6.0]
   */
  class ReLU6Layer : public ClampLayer {
  public:
      explicit ReLU6Layer(const LayerParams &prms) : ClampLayer(prms) {
          max_value = 6.0f;
      }
  
      using ClampLayer::ClampLayer;
  };
  
  
  /**
   * @brief This class represents an element wise operation layer
   */
  class EltwiseLayer : public CNNLayer {
  public:
      /**
       * @enum eOperation
       * @brief Defines possible operations that can be used
       */
      enum eOperation {
          Sum = 0, Prod, Max, Sub, Min, Div, Squared_diff, Floor_mod, Pow,
          Equal, Not_equal, Less, Less_equal, Greater, Greater_equal,
          Logical_AND, Logical_OR, Logical_XOR, Logical_NOT, Mean, Select
      };
  
      /**
       * @brief A type of the operation to use
       */
      eOperation _operation = Sum;
  
      /**
       * @brief A vector of coefficients to scale the operands
       */
      std::vector<float> coeff;
  
      /**
      * @brief Creates a new EltwiseLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a standard crop layer
   */
  class CropLayer : public CNNLayer {
  public:
      /**
       * @brief A vector of dimensions for cropping
       */
      std::vector<int> axis;
      /**
       * @brief A vector of dimensions to be preserved
       */
      std::vector<int> dim;
      /**
       * @brief A vector of offsets for each dimension
       */
      std::vector<int> offset;
  
      /**
       * @brief Creates a new CropLayer instance.
       */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a standard reshape layer
   */
  class ReshapeLayer : public CNNLayer {
  public:
      /**
       * @brief A vector of sizes of the shape
       */
      std::vector<int> shape;
      /**
       * @brief A number of axis to be taken for a reshape
       */
      int axis = 0;
      /**
       * @brief A number of first axises to be taken for a reshape
       */
      int num_axes = -1;
  
      /**
       * @brief Creates a new ReshapeLayer instance.
       */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a standard Tile Layer
   */
  class TileLayer : public CNNLayer {
  public:
      /**
       * @brief An index of the axis to tile
       */
      int axis = -1;
      /**
       * @brief A number of copies to be made
       */
      int tiles = -1;
  
      /**
       * @brief Creates a new TileLayer instance.
       */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
   * @brief This class represents a Layer which performs Scale and Shift
   */
  class ScaleShiftLayer : public WeightableLayer {
  public:
      /**
       * @brief A flag that indicates if the same value is used for all the features. If false, the value is used pixel wise
       */
      unsigned int _broadcast = 0;
  
      /**
       * @brief Creates a new ScaleShiftLayer instance.
       */
      using WeightableLayer::WeightableLayer;
  };
  
  /**
   * @brief This class represents TensorIterator layer
   */
  class TensorIterator : public CNNLayer {
  public:
      struct PortMap {
          // Data map rule
          int from;      /**< Index of exteral data from ins/outs fields of CNNLayer */
          int to;        /**< Index of internal data in iterator body */
  
          // Iteration rule
          int axis;      /**< Axis to iterate throught */
          int stride;    /**< Stride to iterate throught */
          int start;     /**< Start index of iteration range */
          int end;       /**< Last index of iteration range  */
          int part_size; /**< Part size which will be transfered to body subnetwork */
      };
  
      struct Body {
          std::vector<DataPtr> inputs;
          std::vector<DataPtr> outputs;
      };
  
      std::vector<PortMap> input_port_map;
      std::vector<PortMap> output_port_map;
      std::vector<PortMap> back_edges;
  
      Body body;
  
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief Base class for recurrent cell layers
   */
  class RNNCellBase : public WeightableLayer {
  public:
      using WeightableLayer::WeightableLayer;
  
      /**
       * @brief Direct type of recurrent cell (including subtypes)
       * Description of particular cell semantics is in LSTMCell, GRUCell, RNNCell.
       */
      enum CellType {
          LSTM,      /**< Original LSTM cell */
          GRU,       /**< Original GRU cell */
          RNN,       /**< Original RNN cell */
          GRU_LBR,   /**< GRU cell modification. "Linear before reset" */
      };
  
      /** @copybrief CellType */
      CellType cellType = LSTM;
  
      /**
       * @brief Size of hidden state data
       *
       * In case of batch output state tensor will have shape [N, hidden_size]
       */
      int hidden_size = 0;
  
      /**
       * @brief Clip data into range [-clip, clip] on input of activations
       *
       * clip==0.0f means no clipping
       */
      float clip = 0.0f;
      /**
       * @brief Activations used inside recurrent cell
       *
       * Valid values: sigmoid, tanh, relu
       */
      std::vector<std::string> activations;
  
      /**
       * @brief Alpha parameters of activations
       *
       * Respective to activation list.
       */
      std::vector<float> activation_alpha;
  
      /**
       * @brief Beta parameters of activations
       *
       * Respective to activation list.
       */
      std::vector<float> activation_beta;
  };
  
  /**
   * @brief LSTM Cell layer
   *
   * G - number of gates (=4)
   * N - batch size
   * S - state size (=hidden_size)
   *
   * Inputs:
   *   [N,D] Xt - input data
   *   [N,S] Ht-1 - initial hidden state
   *   [N,S] Ct-1 - initial cell state
   *
   * Outputs:
   *   [N,S] Ht - out hidden state
   *   [N,S] Ct - out cell state
   *
   * Weights:
   *   - weights [G,S,D+S]
   *   - biases [G,S]
   * NB!  gates order is FICO {forget, input, candidate, output}
   *
   * activations is {_f, _g, _h}
   * default: {_f=sigm, _g=tanh, _h=tanh}
   *
   * Equations:
   *
   *   *  - matrix mult
   *  (.) - eltwise mult
   *  [,] - concatenation
   *
   * - ft = _f(Wf*[Ht-1, Xt] + Bf)
   * - it = _f(Wi*[Ht-1, Xt] + Bi)
   * - ct = _g(Wc*[Ht-1, Xt] + Bc)
   * - ot = _f(Wo*[Ht-1, Xt] + Bo)
   * - Ct = ft (.) Ct-1 + it (.) ct
   * - Ht = ot (.) _h(Ct)
   */
  using LSTMCell = RNNCellBase;
  
  /**
   * @brief GRU Cell layer
   *
   * G - number of gates (=3)
   * N - batch size
   * S - state size (=hidden_size)
   *
   * Inputs:
   *   [N,D] Xt - input data
   *   [N,S] Ht-1 - initial hidden state
   *
   * Outputs:
   *   [N,S] Ht - out hidden state
   *
   * Weights:
   *   - weights [G,S,D+S]
   *   - biases [G,S]
   * NB!  gates order is ZRH {update, reset, output}
   *
   * activations is {_f, _g}
   * default: {_f=sigm, _g=tanh}
   *
   * Equations:
   *
   *   *  - matrix mult
   *  (.) - eltwise mult
   *  [,] - concatenation
   *
   * - zt = _f(Wz*[Ht-1, Xt] + Bz)
   * - rt = _f(Wr*[Ht-1, Xt] + Br)
   * - ht = _g(Wh*[rt (.) Ht-1, Xt] + Bh)
   * - Ht = (1 - zt) (.) ht + zt (.) Ht-1
   */
  using GRUCell  = RNNCellBase;
  
  /**
   * @brief RNN Cell layer
   *
   * G - number of gates (=1)
   * N - batch size
   * S - state size (=hidden_size)
   *
   * Inputs:
   *   [N,D] Xt - input data
   *   [N,S] Ht-1 - initial hidden state
   *
   * Outputs:
   *   [N,S] Ht - out hidden state
   *
   * Weights:
   *   - weights [G,S,D+S]
   *   - biases [G,S]
   *
   * activations is {_f}
   * default: {_f=tanh}
   *
   * Equations:
   *
   *   *  - matrix mult
   *  [,] - concatenation
   *
   * - Ht = _f(Wi*[Ht-1, Xt] + Bi)
   */
  using RNNCell  = RNNCellBase;
  
  /**
   * @brief Sequence of recurrent cells
   *
   * N  - batch size
   * T  - sequence size
   * S  - state size (=hidden_size)
   * NS - num of state tensors (LSTM=2, GRU/RNN=1)
   * ND - num of direction (BDR=2, WFD/BWD=1)
   *
   * Inputs:
   *   [N,T,D]  Xt - input data
   *   [ND,N,S] Ht-1 - initial hidden state
   *   [ND,N,S] Ct-1 - initial cell state  // if NS==2
   *   [N]      SL - sequence length (optional input)
   *
   * Outputs:
   *   [ND,N,T,S] Xt - input data
   *   [ND,N,S] Ht-1 - initial hidden state
   *   [ND,N,S] Ct-1 - initial cell state  // if NS==2
   *
   * NB! if axis==0 batch and sequense dimensions are swapped (N <-> T) for input and output tensors
   *
   * Weights:
   *   - weights [ND,G,S,D+S]
   *   - biases [ND,G,S]
   * NB! if ND==2 weights are concatenated cell weights [forward_cell_weights, backward_cell_weights]
   *
   */
  class RNNSequenceLayer : public RNNCellBase {
  public:
      using RNNCellBase::RNNCellBase;
  
      /**
       * @brief An axis by which iteration is performed
       * axis=0 means first input/output data blob dimension is sequence
       * axis=1 means first input/output data blob dimension is batch
       */
      unsigned int axis = 1;
  
      /**
       * @brief Direction of iteration through sequence dimension
       */
      enum Direction {
          FWD,  /**< Forward mode. Iterate starts from index 0 with step 1.         */
          BWD,  /**< Backward mode. Iterate starts from last index with step -1.    */
          BDR   /**< Bidirectional mode. First is forward pass, second is backward. */
      };
  
      /** @copybrief Direction */
      Direction direction = FWD;
  };
  
  /**
   * @brief This class represents a Layer which performs Scale and Shift
   */
  class PReLULayer : public WeightableLayer {
  public:
      /**
       * @brief A flag that indicates if the same negative_slope value is used for all the features. If false, the value is used pixel wise
       */
      bool _channel_shared;
  
  public:
      /**
       * @brief A default constructor. Creates a new PReLULayer instance and initializes layer parameters with the given values.
       * @param prms Initial layer parameters
       */
      explicit PReLULayer(const LayerParams &prms) : WeightableLayer(prms), _channel_shared(false) {}
  };
  
  /**
   * @brief This class represents a standard Power Layer
   * Formula is: output = (offset + scale * input) ^ power
   */
  class PowerLayer : public CNNLayer {
  public:
      /**
       * @brief An exponent value
       */
      float power = 1.f;
      /**
       * @brief A scale factor
       */
      float scale = 1.f;
      /**
       * @brief An offset value
       */
      float offset = 0.f;
  
      /**
       * @brief Creates a new PowerLayer instance.
       */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a Batch Normalization Layer
   */
  class BatchNormalizationLayer : public WeightableLayer {
  public:
      /**
       * @brief A small value to add to the variance estimate to avoid division by zero
       */
      float epsilon = 1e-3f;
  
      /**
       * @brief Creates a new BatchNormalizationLayer instance.
       */
      using WeightableLayer::WeightableLayer;
  };
  
  /**
   * @brief This class represents a general matrix multiplication operation layer
   * Formula is: dst := alpha*src1*src2 + beta*src3
   */
  class GemmLayer : public CNNLayer {
  public:
      /**
      * @brief A scale factor of src1 matrix
      */
      float alpha = 1.f;
      /**
      * @brief A scale factor of src3 matrix
      */
      float beta = 1.f;
      /**
      * @brief A flag that indicates if the src1 matrix is to be transposed
      */
      bool transpose_a = false;
      /**
      * @brief A flag that indicates if the src2 matrix is to be transposed
      */
      bool transpose_b = false;
      /**
      * @brief Creates a new GemmLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a standard Pad layer
   * Adds paddings to input tensor
   */
  class PadLayer : public CNNLayer {
  public:
      /**
       * @enum ePadMode
       * @brief Defines possible modes of pad operation
       */
      enum ePadMode {
          Constant = 0, Edge, Reflect, Symmetric
      };
  
      /**
      * @brief Size of padding in the beginning of each axis
      */
      PropertyVector<unsigned int> pads_begin;
      /**
      * @brief Size of padding in the end of each axis
      */
      PropertyVector<unsigned int> pads_end;
      /**
      * @brief Mode of pad operation
      */
      ePadMode pad_mode = Constant;
      /**
      * @brief A pad value which is used for filling in Constant mode
      */
      float pad_value = 0.0f;
      /**
      * @brief Creates a new PadLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a standard Gather layer
   * Gather slices from Dictionary according to Indexes
   */
  class GatherLayer : public CNNLayer {
  public:
      /**
      * @brief The axis in Dictionary to gather Indexes from
      */
      int axis = 0;
      /**
      * @brief Creates a new GatherLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a standard Strided Slice layer
   * Strided Slice picks from input tensor according parameters
   */
  class StridedSliceLayer : public CNNLayer {
  public:
      /**
      * @brief The begin_mask is a bitmask where bit i being 0 means
      * to ignore the begin value and instead use the default value
      */
      std::string begin_mask;
      /**
      * @brief Analogous to begin_mask
      */
      std::string end_mask;
      /**
      * @brief The ellipsis_mask is a bitmask where bit i being 1 means
      * the i-th is actually an ellipsis
      */
      std::string ellipsis_mask;
      /**
      * @brief The new_axis_mask_ is a bitmask where bit i being 1 means
      * the i-th position creates a new 1 dimension shape
      */
      std::string new_axis_mask;
      /**
      * @brief The shrink_axis_mask is a bitmask where bit i being 1 means
      * the i-th position shrinks the dimensionality
      */
      std::string shrink_axis_mask;
  
      /**
      * @brief Creates a new StridedSliceLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  /**
  * @brief This class represents a standard Shuffle Channels layer
  * Shuffle Channels picks from input tensor according parameters
  */
  class ShuffleChannelsLayer : public CNNLayer {
  public:
      /**
      * @brief The axis in tensor to shuffle channels
      */
      int axis = 1;
  
      /**
      * @brief The group of output shuffled channels
      */
      unsigned int group = 1;
  
      /**
      * @brief Creates a new ShuffleChannelsLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a standard Depth To Space layer
  * Depth To Space picks from input tensor according parameters
  */
  class DepthToSpaceLayer : public CNNLayer {
  public:
      /**
      * @brief The group of output shuffled channels
      */
      unsigned int block_size = 1;
  
      /**
      * @brief Creates a new DepthToSpaceLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a standard Space To Depth layer
  * Depth To Space picks from input tensor according parameters
  */
  class SpaceToDepthLayer : public CNNLayer {
  public:
      /**
      * @brief The group of output Space To Depth
      */
      unsigned int block_size = 1;
  
      /**
      * @brief Creates a new SpaceToDepthLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a standard Reverse Sequence layer
  * Reverse Sequence modifies input tensor according parameters
  */
  class ReverseSequenceLayer : public CNNLayer {
  public:
      /**
      * @brief The seq_axis dimension in tensor which is partially reversed
      */
      int seq_axis = 1;
  
      /**
      * @brief The batch_axis dimension in tensor along which reversal is performed
      */
      int batch_axis = 0;
  
      /**
      * @brief Creates a new ReverseSequence instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a OneHot layer
  * Converts input into OneHot representation.
  */
  class OneHotLayer : public CNNLayer {
  public:
      /**
      * @brief A depth of representation
      */
      unsigned int depth = 0;
  
      /**
      * @brief The locations represented by indices in input take value on_value
      */
      float on_value = 1.f;
  
      /**
      * @brief The locations not represented by indices in input take value off_value
      */
      float off_value = 0.f;
  
      /**
      * @brief Define the shape of output tensor
      */
      int axis = -1;
  
      /**
      * @brief Creates a new OneHot instance
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a standard RangeLayer layer
  * RangeLayer modifies input tensor dimensions according parameters
  */
  class RangeLayer : public CNNLayer {
  public:
      /**
      * @brief Creates a new RangeLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a standard Fill layer
  * RFill modifies input tensor according parameters
  */
  class FillLayer : public CNNLayer {
  public:
      /**
      * @brief Creates a new Fill instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a SelectLayer layer
  * SelectLayer layer takes elements from the second (“then”) or the third (“else”) input based on condition mask (“cond”) provided in the first input.
  * The “cond” tensor is broadcasted to “then” and “else” tensors.
  * The output tensor shape is equal to broadcasted shape of “cond”, “then” and “else”.
  */
  class SelectLayer : public CNNLayer {
  public:
      /**
      * @brief Creates a new SelectLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a standard Broadcast layer
  * Broadcast modifies input tensor dimensions according parameters
  */
  class BroadcastLayer : public CNNLayer {
  public:
      /**
      * @brief Creates a new Broadcast instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  /**
   * @brief This class represents a quantization operation layer
   * Element-wise linear quantization of floating point input values into a descrete set of floating point values
   */
  class QuantizeLayer : public CNNLayer {
  public:
      /**
      * @brief The number of quantization levels
      */
      int levels = 1;
  
      /**
      * @brief Creates a new QuantizeLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a standard Math layers
  * Math modifies input tensor dimensions according parameters
  */
  class MathLayer : public CNNLayer {
  public:
      /**
      * @brief Creates a new Math instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
  * @brief This class represents a standard Reduce layers
  * Reduce modifies input tensor according parameters
  */
  class ReduceLayer : public CNNLayer {
  public:
      /**
      * @brief The keep_dims dimension in tensor which is partially reversed
      */
      bool keep_dims = true;
  
      /**
      * @brief Creates a new Reduce instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  /**
   * @brief This class represents a standard TopK layer
   * TopK picks top K values from input tensor according parameters
   */
  class TopKLayer : public CNNLayer {
  public:
      /**
      * @brief The mode could be 'max' or 'min'
      */
      std::string mode;
      /**
      * @brief top K values sort mode could be 'value' or 'index'
      */
      std::string sort;
      /**
      * @brief The axis dimension in tensor which is top K values are picked
      */
      int axis = -1;
  
      /**
      * @brief Creates a new TopKLayer instance.
      */
      using CNNLayer::CNNLayer;
  };
  
  
  }  // namespace InferenceEngine
  