partial_shape.hpp
1 //*****************************************************************************
2 // Copyright 2017-2020 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //*****************************************************************************
16 
17 #pragma once
18 
19 #include <stddef.h>
20 
21 #include "ngraph/attribute_adapter.hpp"
22 #include "ngraph/dimension.hpp"
23 #include "ngraph/op/util/attr_types.hpp"
24 #include "ngraph/rank.hpp"
25 #include "ngraph/shape.hpp"
26 
27 namespace ngraph
28 {
29  namespace op
30  {
31  struct AutoBroadcastSpec;
32  }
33 
34  /// \brief Class representing a shape that may be partially or totally dynamic.
35  ///
36  /// XXX: THIS CLASS IS EXPERIMENTAL AND THE ENTIRE DESIGN IS SUBJECT TO CHANGE.
37  ///
38  /// A PartialShape may have:
39  ///
40  /// \li Dynamic rank. (Informal notation: `?`)
41  /// \li Static rank, but dynamic dimensions on some or all axes.
42  /// (Informal notation examples: `{1,2,?,4}`, `{?,?,?}`)
43  /// \li Static rank, and static dimensions on all axes.
44  /// (Informal notation examples: `{1,2,3,4}`, `{6}`, `{}`)
45  class NGRAPH_API PartialShape
46  {
47  public:
48  /// \brief Constructs a shape with static rank from an initializer list of Dimension.
49  /// \param init The Dimension values for the constructed shape.
50  ///
51  /// Examples:
52  ///
53  /// \code{.cpp}
54  /// PartialShape s{2,3,4}; // rank=3, all dimensions static
55  /// PartialShape s{}; // rank=0
56  /// PartialShape s{2,Dimension::dynamic(),3}; // rank=3, dimension 1 dynamic
57  /// \endcode
58  PartialShape(std::initializer_list<Dimension> init);
59 
60  /// \brief Constructs a PartialShape with static rank from a vector of Dimension.
61  /// \param dimensions The Dimension values for the constructed shape.
62  PartialShape(const std::vector<Dimension>& dimensions);
63 
64  /// \brief Constructs a PartialShape with static rank from a vector of dimensions values.
65  /// \param dimensions The Dimension values for the constructed shape.
66  PartialShape(const std::vector<Dimension::value_type>& dimensions);
67 
68  /// \brief Constructs a static PartialShape with zero rank (the shape of a scalar).
70 
71  /// \brief Constructs a static PartialShape from a Shape.
72  /// \param shape The Shape to convert into PartialShape.
73  PartialShape(const Shape& shape);
74 
75  /// \brief Check if this shape is static.
76  /// \return `true` if this shape is static, else `false`.
77  ///
78  /// A shape is considered static if it has static rank, and all dimensions of the shape
79  /// are static.
80  bool is_static() const;
81 
82  /// \brief Check if this shape is dynamic.
83  /// \return `false` if this shape is static, else `true`.
84  ///
85  /// A shape is considered static if it has static rank, and all dimensions of the shape
86  /// are static.
87  bool is_dynamic() const { return !is_static(); }
88  /// \brief Get the rank of the shape.
89  /// \return The rank of the shape. This will be Rank::dynamic() if the rank of
90  /// the shape is dynamic.
91  Rank rank() const { return m_rank_is_static ? Rank(m_dimensions.size()) : Rank::dynamic(); }
92  /// \brief Construct a PartialShape with the given rank and all dimensions (if any) dynamic.
93  /// \return A PartialShape with the given rank, and all dimensions (if any) dynamic.
94  static PartialShape dynamic(Rank r = Rank::dynamic());
95  /// \brief Check whether this shape is compatible with the argument, i.e., whether it is
96  /// possible to merge them.
97  /// \param s The shape to be checked for compatibility with this shape.
98  /// \return `true` if this shape is compatible with `s`, else `false`.
99  ///
100  /// Two shapes are compatible if
101  /// \li one or both of them has dynamic rank, or
102  /// \li both shapes have dynamic and equal rank, and their dimensions are elementwise
103  /// compatible (see Dimension::compatible()).
104  bool compatible(const PartialShape& s) const;
105 
106  /// \brief Check whether this shape represents the same scheme as the argument.
107  /// \param s The shape whose scheme is being compared with this shape.
108  /// \return `true` if this shape represents the same scheme as `s`, else `false`.
109  ///
110  /// Two shapes `s1` and `s2` represent the same scheme if
111  /// \li they both have dynamic rank, or
112  /// \li they both have static and equal rank `r`, and for every `i` from `0` to `r-1`,
113  /// `s1[i]` represents the same scheme as `s2[i]` (see Dimension::same_scheme()).
114  bool same_scheme(const PartialShape& s) const;
115 
116  /// \brief Check whether this shape is a relaxation of the argument.
117  /// \param s The shape which is being compared against this shape.
118  /// \return `true` if this shape relaxes `s`, else `false`.
119  ///
120  /// Intuitively, a PartialShape `s1` is said to _relax_ `s2` (or _is a
121  /// relaxation_ of `s2`) if it is "more permissive" than `s2`. In other
122  /// words, `s1` is a relaxation of `s2` if anything you can form by
123  /// plugging things into the dynamic dimensions of `s2` is also
124  /// something you can form by plugging things into the dynamic
125  /// dimensions of `s1`, but not necessarily the other way around.
126  ///
127  /// `s1.relaxes(s2)` is equivalent to `s2.refines(s1)`.
128  ///
129  /// Formally, PartialShape `s1` is said to _relax_ PartialShape `s2`
130  /// if:
131  /// \li For every `i` from `0` to `r-1`,
132  /// either `s1[i]` contains s2[i].
133  bool relaxes(const PartialShape& s) const;
134 
135  /// \brief Check whether this shape is a refinement of the argument.
136  /// \param s The shape which is being compared against this shape.
137  /// \return `true` if this shape refines `s`, else `false`.
138  ///
139  /// Intuitively, a PartialShape `s1` is said to _relax_ `s2` (or _is a
140  /// relaxation_ of `s2`) if it is "less permissive" than `s2`. In other
141  /// words, `s1` is a relaxation of `s2` if anything you can form by
142  /// plugging things into the dynamic dimensions of `s1` is also
143  /// something you can form by plugging things into the dynamic
144  /// dimensions of `s2`, but not necessarily the other way around.
145  ///
146  /// `s1.refines(s2)` is equivalent to `s2.relaxes(s1)`.
147  ///
148  /// Formally, PartialShape `s1` is said to _refine_ PartialShape `s2`
149  /// if:
150  /// \li `s2` has dynamic rank, or
151  /// \li `s1` and `s2` both have static rank `r`, and for every `i` from `0` to `r-1`,
152  /// either `s2[i]` is dynamic, or `s1[i]` == `s2[i]`.
153  bool refines(const PartialShape& s) const;
154 
155  /// \brief Checks that this shape's rank is compatible with `r`, and, if this shape's
156  /// rank is dynamic and `r` is static, updates this shape to have a rank of `r`
157  /// with dimensions all dynamic.
158  /// \return `true` if this shape's rank is compatible with `r`, else `false`.
159  bool merge_rank(Rank r);
160 
161  /// \brief Convert a static PartialShape to a Shape.
162  /// \return A new Shape `s` where `s[i] = size_t((*this)[i])`.
163  /// \throws std::invalid_argument If this PartialShape is dynamic.
164  Shape to_shape() const;
165 
166  /// \brief Returns `true` if all static dimensions of the tensor are non-negative, else
167  /// `false`.
168  bool all_non_negative() const;
169 
170  /// \brief Index operator for PartialShape.
171  /// \param i The index of the dimension being selected.
172  /// \return A reference to the `i`th Dimension of this shape.
173  const Dimension& operator[](size_t i) const;
174  /// \brief Index operator for PartialShape.
175  /// \param i The index of the dimension being selected.
176  /// \return A reference to the `i`th Dimension of this shape.
177  Dimension& operator[](size_t i);
178  /// \brief Returns a vector of the dimensions. This has no meaning if dynamic.
179  explicit operator std::vector<Dimension>() const { return m_dimensions; }
180  friend NGRAPH_API std::ostream& operator<<(std::ostream& str, const PartialShape& shape);
181  friend PartialShape operator+(const PartialShape& s1, const PartialShape& s2);
182  bool operator==(const PartialShape& partial_shape) const;
183  bool operator!=(const PartialShape& partial_shape) const;
184  /// Get the max bounding shape
186  /// Get the min bounding shape
188  /// Get the unique shape
189  Shape get_shape() const;
190 
191  /// \brief Try to merge one shape into another.
192  /// \param[in,out] dst The shape that `src` will be merged into.
193  /// \param src The shape that will be merged into `dst`.
194  /// \return `true` if merging succeeds, else `false`.
195  ///
196  /// Merges `src` into `dst`, returning `true` on success and `false` on failure. If
197  /// `false` is returned, the effect on `dst` is unspecified.
198  ///
199  /// To merge two partial shapes `s1` and `s2` is to find the most permissive partial shape
200  /// `s` that is no more permissive than `s1` or `s2`, if `s` exists. For example:
201  ///
202  /// \code
203  /// merge(?,?) -> ?
204  /// merge(?,{?,?}) -> {?,?}
205  /// merge({?,?},{?,?}) -> {?,?}
206  /// merge({1,2,3,4},?) -> {1,2,3,4}
207  /// merge({1,2},{1,?}) -> {1,2}
208  /// merge({1,2,?,?},{1,?,3,?}) -> {1,2,3,?}
209  /// merge({1,2,3},{1,2,3}) -> {1,2,3}
210  ///
211  /// merge({1,?},{2,?}) fails [dimension 0 constraints are inconsistent]
212  /// merge({?,?},{?,?,?}) fails [ranks are inconsistent]
213  /// \endcode
214  ///
215  /// This function (merge_into) performs the "merge" operation described above on `dst` and
216  /// `src`, but overwrites `dst` with the result and returns `true` if merging is
217  /// successful; if merging is unsuccessful, the function returns `false` and may make
218  /// unspecified changes to `dst`.
219  static bool merge_into(PartialShape& dst, const PartialShape& src);
220 
221  /// \brief Try to merge one shape into another along with implicit broadcasting
223  const PartialShape& src,
224  const op::AutoBroadcastSpec& autob);
225 
226  private:
227  // Private constructor for PartialShape::dynamic().
228  PartialShape(bool rank_is_static, const std::vector<Dimension>& dimensions);
229 
230  // True if the shape's rank is static.
231  bool m_rank_is_static;
232 
233  /// \brief Shape types. The shape type is lazily evaluated by calling the is_static()
234  /// method.
235  ///
236  /// \details It is highly recommended to avoid using the Dimension& operator[](size_t)
237  /// operator. It sets the shape type to SHAPE_IS_UPDATED and disables shape type caching.
238  /// Thus, the is_static method will have linear complexity because the shape is not
239  /// guaranteed to remain static or dynamic.
240  mutable enum class ShapeType {
241  SHAPE_IS_UNKNOWN, // The shape type is unknown and should be calculated by checking all
242  // dimensions.
243  SHAPE_IS_UPDATED, // User has retained a link to one dimension. Therefore, we can't
244  // guarantee that the shape will remain static or dynamic, and its
245  // type will always be evaluated.
246  SHAPE_IS_STATIC, // The shape type is known and static. Also there are no any retained
247  // dimensions by non-constant reference.
248  SHAPE_IS_DYNAMIC // The shape type is dynamic and there are no any retained dimensions
249  // by non-constant reference.
250  } m_shape_type{ShapeType::SHAPE_IS_UNKNOWN};
251 
252  // Shape dimensions. This has no meaning if m_rank_is_static is false.
253  std::vector<Dimension> m_dimensions;
254  };
255 
256  /// \brief Elementwise addition of two PartialShape objects.
257  /// \param s1 Left operand for addition.
258  /// \param s2 Right operand for addition.
259  /// \return The result of elementwise adding `s1` to `s2` (see description).
260  /// \throws std::invalid_argument If `s1` and `s2` have inconsistent ranks.
261  ///
262  /// \li If `s1` or `s2` has dynamic rank, returns PartialShape::dynamic().
263  /// \li If `s1 and `s2` both have static rank, and their ranks are unequal, throws
264  /// std::invalid_argument.
265  /// \li If `s1` and `s2` both have static rank, and their ranks are equal,
266  /// returns a new shape whose `i`th dimension is `s1[i] + s2[i]`.
267  PartialShape operator+(const PartialShape& s1, const PartialShape& s2);
268 
269  /// \brief Inserts a human-readable representation of a PartialShape into an output stream.
270  /// \param str The output stream targeted for insertion.
271  /// \param shape The shape to be inserted into `str`.
272  /// \return A reference to `str` after insertion.
273  ///
274  /// The output to the stream is in "informal" notation. In other words:
275  ///
276  /// \li If `shape` has dynamic rank, inserts the string `?`.
277  /// \li If `shape` has static rank, inserts the string `{`, then inserts each dimension
278  /// of `shape` into the output stream separated by commas, then inserts `}`.
279  ///
280  /// Example:
281  ///
282  /// \code{.cpp}
283  /// PartialShape s1{PartialShape::dynamic())};
284  /// PartialShape s2{};
285  /// PartialShape s3{1,Dimension::dynamic(),2,3};
286  /// PartialShape s4{2,3,4};
287  /// std::cout << s1 << std::endl
288  /// << s2 << std::endl
289  /// << s3 << std::endl
290  /// << s4 << std::endl;
291  /// \endcode
292  ///
293  /// Output:
294  ///
295  /// \code
296  /// ?
297  /// {}
298  /// {1,?,2,3}
299  /// {2,3,4}
300  /// \endcode
301  NGRAPH_API
302  std::ostream& operator<<(std::ostream& str, const PartialShape& shape);
303 
304  template <>
305  class NGRAPH_API AttributeAdapter<PartialShape> : public ValueAccessor<std::vector<int64_t>>
306  {
307  public:
308  AttributeAdapter(PartialShape& value)
309  : m_ref(value)
310  {
311  }
312 
313  const std::vector<int64_t>& get() override;
314  void set(const std::vector<int64_t>& value) override;
315  static constexpr DiscreteTypeInfo type_info{"AttributeAdapter<PartialShape>", 0};
316  const DiscreteTypeInfo& get_type_info() const override { return type_info; }
317  operator PartialShape&() { return m_ref; }
318  protected:
319  PartialShape& m_ref;
320  std::vector<int64_t> m_buffer;
321  bool m_buffer_valid{false};
322  };
323 }
ngraph::PartialShape::PartialShape
PartialShape(const std::vector< Dimension > &dimensions)
Constructs a PartialShape with static rank from a vector of Dimension.
ngraph::PartialShape::PartialShape
PartialShape(const std::vector< Dimension::value_type > &dimensions)
Constructs a PartialShape with static rank from a vector of dimensions values.
ngraph::AttributeAdapter< PartialShape >::set
void set(const std::vector< int64_t > &value) override
Sets the value.
ngraph::PartialShape::merge_rank
bool merge_rank(Rank r)
Checks that this shape's rank is compatible with r, and, if this shape's rank is dynamic and r is sta...
ngraph::PartialShape::same_scheme
bool same_scheme(const PartialShape &s) const
Check whether this shape represents the same scheme as the argument.
ngraph::PartialShape::PartialShape
PartialShape()
Constructs a static PartialShape with zero rank (the shape of a scalar).
ngraph::PartialShape
Class representing a shape that may be partially or totally dynamic.
Definition: partial_shape.hpp:46
ngraph::PartialShape::merge_into
static bool merge_into(PartialShape &dst, const PartialShape &src)
Try to merge one shape into another.
ngraph::Rank
Dimension Rank
Alias for Dimension, used when the value represents the number of axes in a shape,...
Definition: rank.hpp:27
ngraph::PartialShape::refines
bool refines(const PartialShape &s) const
Check whether this shape is a refinement of the argument.
ngraph::PartialShape::compatible
bool compatible(const PartialShape &s) const
Check whether this shape is compatible with the argument, i.e., whether it is possible to merge them.
ngraph::PartialShape::is_static
bool is_static() const
Check if this shape is static.
ngraph::PartialShape::all_non_negative
bool all_non_negative() const
Returns true if all static dimensions of the tensor are non-negative, else false.
ngraph::PartialShape::broadcast_merge_into
static bool broadcast_merge_into(PartialShape &dst, const PartialShape &src, const op::AutoBroadcastSpec &autob)
Try to merge one shape into another along with implicit broadcasting.
ngraph::PartialShape::to_shape
Shape to_shape() const
Convert a static PartialShape to a Shape.
ngraph::PartialShape::get_shape
Shape get_shape() const
Get the unique shape.
ngraph::PartialShape::rank
Rank rank() const
Get the rank of the shape.
Definition: partial_shape.hpp:91
ngraph::DiscreteTypeInfo
Definition: type.hpp:39
ngraph::Shape
Shape for a tensor.
Definition: shape.hpp:31
ngraph::Dimension
Class representing a dimension, which may be dynamic (undetermined until runtime),...
Definition: dimension.hpp:35
ngraph
The Intel nGraph C++ API.
Definition: attribute_adapter.hpp:28
ngraph::PartialShape::PartialShape
PartialShape(std::initializer_list< Dimension > init)
Constructs a shape with static rank from an initializer list of Dimension.
ngraph::AttributeAdapter< PartialShape >::get
const std::vector< int64_t > & get() override
Returns the value.
ngraph::PartialShape::operator[]
const Dimension & operator[](size_t i) const
Index operator for PartialShape.
ngraph::PartialShape::PartialShape
PartialShape(const Shape &shape)
Constructs a static PartialShape from a Shape.
ngraph::PartialShape::get_min_shape
Shape get_min_shape() const
Get the min bounding shape.
ngraph::PartialShape::get_max_shape
Shape get_max_shape() const
Get the max bounding shape.
ngraph::PartialShape::relaxes
bool relaxes(const PartialShape &s) const
Check whether this shape is a relaxation of the argument.
ngraph::PartialShape::is_dynamic
bool is_dynamic() const
Check if this shape is dynamic.
Definition: partial_shape.hpp:87
ngraph::PartialShape::operator[]
Dimension & operator[](size_t i)
Index operator for PartialShape.
ngraph::PartialShape::dynamic
static PartialShape dynamic(Rank r=Rank::dynamic())
Construct a PartialShape with the given rank and all dimensions (if any) dynamic.