partial_shape.hpp
1 //*****************************************************************************
2 // Copyright 2017-2021 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  using Dimensions = std::vector<Dimension>;
48 
49  public:
50  using iterator = Dimensions::iterator;
51  using const_iterator = Dimensions::const_iterator;
52  using reverse_iterator = Dimensions::reverse_iterator;
53  using const_reverse_iterator = Dimensions::const_reverse_iterator;
54 
55  /// \brief Constructs a shape with static rank from an initializer list of Dimension.
56  /// \param init The Dimension values for the constructed shape.
57  ///
58  /// Examples:
59  ///
60  /// \code{.cpp}
61  /// PartialShape s{2,3,4}; // rank=3, all dimensions static
62  /// PartialShape s{}; // rank=0
63  /// PartialShape s{2,Dimension::dynamic(),3}; // rank=3, dimension 1 dynamic
64  /// \endcode
65  PartialShape(std::initializer_list<Dimension> init);
66 
67  /// \brief Constructs a PartialShape with static rank from a vector of Dimension.
68  /// \param dimensions The Dimension values for the constructed shape.
69  PartialShape(const std::vector<Dimension>& dimensions);
70 
71  /// \brief Constructs a PartialShape with static rank from a vector of dimensions values.
72  /// \param dimensions The Dimension values for the constructed shape.
73  PartialShape(const std::vector<Dimension::value_type>& dimensions);
74 
75  /// \brief Constructs a static PartialShape with zero rank (the shape of a scalar).
77 
78  /// \brief Constructs a static PartialShape from a Shape.
79  /// \param shape The Shape to convert into PartialShape.
80  PartialShape(const Shape& shape);
81 
82  /// \brief Check if this shape is static.
83  /// \return `true` if this shape is static, else `false`.
84  ///
85  /// A shape is considered static if it has static rank, and all dimensions of the shape
86  /// are static.
87  bool is_static() const;
88 
89  /// \brief Check if this shape is dynamic.
90  /// \return `false` if this shape is static, else `true`.
91  ///
92  /// A shape is considered static if it has static rank, and all dimensions of the shape
93  /// are static.
94  bool is_dynamic() const { return !is_static(); }
95  /// \brief Get the rank of the shape.
96  /// \return The rank of the shape. This will be Rank::dynamic() if the rank of
97  /// the shape is dynamic.
98  Rank rank() const { return m_rank_is_static ? Rank(m_dimensions.size()) : Rank::dynamic(); }
99  /// \brief Construct a PartialShape with the given rank and all dimensions (if any) dynamic.
100  /// \return A PartialShape with the given rank, and all dimensions (if any) dynamic.
102  /// \brief Check whether this shape is compatible with the argument, i.e., whether it is
103  /// possible to merge them.
104  /// \param s The shape to be checked for compatibility with this shape.
105  /// \return `true` if this shape is compatible with `s`, else `false`.
106  ///
107  /// Two shapes are compatible if
108  /// \li one or both of them has dynamic rank, or
109  /// \li both shapes have dynamic and equal rank, and their dimensions are elementwise
110  /// compatible (see Dimension::compatible()).
111  bool compatible(const PartialShape& s) const;
112 
113  /// \brief Check whether this shape represents the same scheme as the argument.
114  /// \param s The shape whose scheme is being compared with this shape.
115  /// \return `true` if this shape represents the same scheme as `s`, else `false`.
116  ///
117  /// Two shapes `s1` and `s2` represent the same scheme if
118  /// \li they both have dynamic rank, or
119  /// \li they both have static and equal rank `r`, and for every `i` from `0` to `r-1`,
120  /// `s1[i]` represents the same scheme as `s2[i]` (see Dimension::same_scheme()).
121  bool same_scheme(const PartialShape& s) const;
122 
123  /// \brief Check whether this shape is a relaxation of the argument.
124  /// \param s The shape which is being compared against this shape.
125  /// \return `true` if this shape relaxes `s`, else `false`.
126  ///
127  /// Intuitively, a PartialShape `s1` is said to _relax_ `s2` (or _is a
128  /// relaxation_ of `s2`) if it is "more permissive" than `s2`. In other
129  /// words, `s1` is a relaxation of `s2` if anything you can form by
130  /// plugging things into the dynamic dimensions of `s2` is also
131  /// something you can form by plugging things into the dynamic
132  /// dimensions of `s1`, but not necessarily the other way around.
133  ///
134  /// `s1.relaxes(s2)` is equivalent to `s2.refines(s1)`.
135  ///
136  /// Formally, PartialShape `s1` is said to _relax_ PartialShape `s2`
137  /// if:
138  /// \li For every `i` from `0` to `r-1`,
139  /// either `s1[i]` contains s2[i].
140  bool relaxes(const PartialShape& s) const;
141 
142  /// \brief Check whether this shape is a refinement of the argument.
143  /// \param s The shape which is being compared against this shape.
144  /// \return `true` if this shape refines `s`, else `false`.
145  ///
146  /// Intuitively, a PartialShape `s1` is said to _relax_ `s2` (or _is a
147  /// relaxation_ of `s2`) if it is "less permissive" than `s2`. In other
148  /// words, `s1` is a relaxation of `s2` if anything you can form by
149  /// plugging things into the dynamic dimensions of `s1` is also
150  /// something you can form by plugging things into the dynamic
151  /// dimensions of `s2`, but not necessarily the other way around.
152  ///
153  /// `s1.refines(s2)` is equivalent to `s2.relaxes(s1)`.
154  ///
155  /// Formally, PartialShape `s1` is said to _refine_ PartialShape `s2`
156  /// if:
157  /// \li `s2` has dynamic rank, or
158  /// \li `s1` and `s2` both have static rank `r`, and for every `i` from `0` to `r-1`,
159  /// either `s2[i]` is dynamic, or `s1[i]` == `s2[i]`.
160  bool refines(const PartialShape& s) const;
161 
162  /// \brief Checks that this shape's rank is compatible with `r`, and, if this shape's
163  /// rank is dynamic and `r` is static, updates this shape to have a rank of `r`
164  /// with dimensions all dynamic.
165  /// \return `true` if this shape's rank is compatible with `r`, else `false`.
166  bool merge_rank(Rank r);
167 
168  /// \brief Convert a static PartialShape to a Shape.
169  /// \return A new Shape `s` where `s[i] = size_t((*this)[i])`.
170  /// \throws std::invalid_argument If this PartialShape is dynamic.
171  Shape to_shape() const;
172 
173  /// \brief Returns `true` if all static dimensions of the tensor are non-negative, else
174  /// `false`.
175  bool all_non_negative() const;
176 
177  /// \brief Index operator for PartialShape.
178  /// \param i The index of the dimension being selected.
179  /// \return A reference to the `i`th Dimension of this shape.
180  const Dimension& operator[](size_t i) const;
181  /// \brief Index operator for PartialShape.
182  /// \param i The index of the dimension being selected.
183  /// \return A reference to the `i`th Dimension of this shape.
184  Dimension& operator[](size_t i);
185  /// \brief Returns a vector of the dimensions. This has no meaning if dynamic.
186  explicit operator std::vector<Dimension>() const { return m_dimensions; }
187  friend NGRAPH_API std::ostream& operator<<(std::ostream& str, const PartialShape& shape);
188  friend PartialShape operator+(const PartialShape& s1, const PartialShape& s2);
189  bool operator==(const PartialShape& partial_shape) const;
190  bool operator!=(const PartialShape& partial_shape) const;
191  /// Get the max bounding shape
193  /// Get the min bounding shape
195  /// Get the unique shape
196  Shape get_shape() const;
197 
198  /// \brief Try to merge one shape into another.
199  /// \param[in,out] dst The shape that `src` will be merged into.
200  /// \param src The shape that will be merged into `dst`.
201  /// \return `true` if merging succeeds, else `false`.
202  ///
203  /// Merges `src` into `dst`, returning `true` on success and `false` on failure. If
204  /// `false` is returned, the effect on `dst` is unspecified.
205  ///
206  /// To merge two partial shapes `s1` and `s2` is to find the most permissive partial shape
207  /// `s` that is no more permissive than `s1` or `s2`, if `s` exists. For example:
208  ///
209  /// \code
210  /// merge(?,?) -> ?
211  /// merge(?,{?,?}) -> {?,?}
212  /// merge({?,?},{?,?}) -> {?,?}
213  /// merge({1,2,3,4},?) -> {1,2,3,4}
214  /// merge({1,2},{1,?}) -> {1,2}
215  /// merge({1,2,?,?},{1,?,3,?}) -> {1,2,3,?}
216  /// merge({1,2,3},{1,2,3}) -> {1,2,3}
217  ///
218  /// merge({1,?},{2,?}) fails [dimension 0 constraints are inconsistent]
219  /// merge({?,?},{?,?,?}) fails [ranks are inconsistent]
220  /// \endcode
221  ///
222  /// This function (merge_into) performs the "merge" operation described above on `dst` and
223  /// `src`, but overwrites `dst` with the result and returns `true` if merging is
224  /// successful; if merging is unsuccessful, the function returns `false` and may make
225  /// unspecified changes to `dst`.
226  static bool merge_into(PartialShape& dst, const PartialShape& src);
227 
228  /// \brief Try to merge one shape into another along with implicit broadcasting
230  const PartialShape& src,
231  const op::AutoBroadcastSpec& autob);
232 
233  /// \brief Returns a read/write iterator that points to the first
234  /// element in the shape. Iteration is done in ordinary
235  /// element order.
236  iterator begin() noexcept { return m_dimensions.begin(); }
237  /// \brief Returns a read-only (constant) iterator that points to the
238  /// first element in the shape. Iteration is done in ordinary
239  /// element order.
240  const_iterator begin() const noexcept { return cbegin(); }
241  /// \brief Returns a read/write iterator that points one past the last
242  /// element in the shape. Iteration is done in ordinary
243  /// element order.
244  iterator end() noexcept { return m_dimensions.end(); }
245  /// \brief Returns a read-only (constant) iterator that points one past
246  /// the last element in the shape. Iteration is done in ordinary
247  /// element order.
248  const_iterator end() const noexcept { return cend(); }
249  /// \brief Returns a read/write reverse iterator that points to the
250  /// last element in the shape. Iteration is done in reverse
251  /// element order.
252  reverse_iterator rbegin() noexcept { return m_dimensions.rbegin(); }
253  /// \brief Returns a read-only (constant) reverse iterator that points
254  /// to the last element in the shape. Iteration is done in
255  /// reverse element order.
256  const_reverse_iterator rbegin() const noexcept { return crbegin(); }
257  /// \brief Returns a read/write reverse iterator that points to one
258  /// before the first element in the shape. Iteration is done
259  /// in reverse element order.
260  reverse_iterator rend() noexcept { return m_dimensions.rend(); }
261  /// \brief Returns a read-only (constant) reverse iterator that points
262  /// to one before the first element in the shape. Iteration
263  /// is done in reverse element order.
264  const_reverse_iterator rend() const noexcept { return crend(); }
265  /// \brief Returns a read-only (constant) iterator that points to the
266  /// first element in the shape. Iteration is done in ordinary
267  /// element order.
268  const_iterator cbegin() const noexcept { return m_dimensions.cbegin(); }
269  /// \brief Returns a read-only (constant) iterator that points one past
270  /// the last element in the shape. Iteration is done in ordinary
271  /// element order.
272  const_iterator cend() const noexcept { return m_dimensions.cend(); }
273  /// \brief Returns a read-only (constant) reverse iterator that points
274  /// to the last element in the shape. Iteration is done in
275  /// reverse element order.
276  const_reverse_iterator crbegin() const noexcept { return m_dimensions.crbegin(); }
277  /// \brief Returns a read-only (constant) reverse iterator that points
278  /// to one before the first element in the shape. Iteration
279  /// is done in reverse element order.
280  const_reverse_iterator crend() const noexcept { return m_dimensions.crend(); }
281  private:
282  // Private constructor for PartialShape::dynamic().
283  PartialShape(bool rank_is_static, const std::vector<Dimension>& dimensions);
284 
285  // True if the shape's rank is static.
286  bool m_rank_is_static;
287 
288  /// \brief Shape types. The shape type is lazily evaluated by calling the is_static()
289  /// method.
290  ///
291  /// \details It is highly recommended to avoid using the Dimension& operator[](size_t)
292  /// operator. It sets the shape type to SHAPE_IS_UPDATED and disables shape type caching.
293  /// Thus, the is_static method will have linear complexity because the shape is not
294  /// guaranteed to remain static or dynamic.
295  mutable enum class ShapeType {
296  SHAPE_IS_UNKNOWN, // The shape type is unknown and should be calculated by checking all
297  // dimensions.
298  SHAPE_IS_UPDATED, // User has retained a link to one dimension. Therefore, we can't
299  // guarantee that the shape will remain static or dynamic, and its
300  // type will always be evaluated.
301  SHAPE_IS_STATIC, // The shape type is known and static. Also there are no any retained
302  // dimensions by non-constant reference.
303  SHAPE_IS_DYNAMIC // The shape type is dynamic and there are no any retained dimensions
304  // by non-constant reference.
305  } m_shape_type{ShapeType::SHAPE_IS_UNKNOWN};
306 
307  // Shape dimensions. This has no meaning if m_rank_is_static is false.
308  Dimensions m_dimensions;
309  };
310 
311  /// \brief Elementwise addition of two PartialShape objects.
312  /// \param s1 Left operand for addition.
313  /// \param s2 Right operand for addition.
314  /// \return The result of elementwise adding `s1` to `s2` (see description).
315  /// \throws std::invalid_argument If `s1` and `s2` have inconsistent ranks.
316  ///
317  /// \li If `s1` or `s2` has dynamic rank, returns PartialShape::dynamic().
318  /// \li If `s1 and `s2` both have static rank, and their ranks are unequal, throws
319  /// std::invalid_argument.
320  /// \li If `s1` and `s2` both have static rank, and their ranks are equal,
321  /// returns a new shape whose `i`th dimension is `s1[i] + s2[i]`.
323 
324  /// \brief Inserts a human-readable representation of a PartialShape into an output stream.
325  /// \param str The output stream targeted for insertion.
326  /// \param shape The shape to be inserted into `str`.
327  /// \return A reference to `str` after insertion.
328  ///
329  /// The output to the stream is in "informal" notation. In other words:
330  ///
331  /// \li If `shape` has dynamic rank, inserts the string `?`.
332  /// \li If `shape` has static rank, inserts the string `{`, then inserts each dimension
333  /// of `shape` into the output stream separated by commas, then inserts `}`.
334  ///
335  /// Example:
336  ///
337  /// \code{.cpp}
338  /// PartialShape s1{PartialShape::dynamic())};
339  /// PartialShape s2{};
340  /// PartialShape s3{1,Dimension::dynamic(),2,3};
341  /// PartialShape s4{2,3,4};
342  /// std::cout << s1 << std::endl
343  /// << s2 << std::endl
344  /// << s3 << std::endl
345  /// << s4 << std::endl;
346  /// \endcode
347  ///
348  /// Output:
349  ///
350  /// \code
351  /// ?
352  /// {}
353  /// {1,?,2,3}
354  /// {2,3,4}
355  /// \endcode
356  NGRAPH_API
357  std::ostream& operator<<(std::ostream& str, const PartialShape& shape);
358 
359  template <>
360  class NGRAPH_API AttributeAdapter<PartialShape> : public ValueAccessor<std::vector<int64_t>>
361  {
362  public:
364  : m_ref(value)
365  {
366  }
367 
368  const std::vector<int64_t>& get() override;
369  void set(const std::vector<int64_t>& value) override;
370  static constexpr DiscreteTypeInfo type_info{"AttributeAdapter<PartialShape>", 0};
371  const DiscreteTypeInfo& get_type_info() const override { return type_info; }
372  operator PartialShape&() { return m_ref; }
373  protected:
374  PartialShape& m_ref;
375  std::vector<int64_t> m_buffer;
376  bool m_buffer_valid{false};
377  };
378 }
void set(const std::vector< int64_t > &value) override
Sets the value.
const std::vector< int64_t > & get() override
Returns the value.
An AttributeAdapter "captures" an attribute as an AT& and makes it available as a ValueAccessor<VAT>.
Definition: attribute_adapter.hpp:171
Class representing a dimension, which may be dynamic (undetermined until runtime),...
Definition: dimension.hpp:35
static Dimension dynamic()
Create a dynamic dimension.
Definition: dimension.hpp:130
Class representing a shape that may be partially or totally dynamic.
Definition: partial_shape.hpp:46
bool all_non_negative() const
Returns true if all static dimensions of the tensor are non-negative, else false.
Rank rank() const
Get the rank of the shape.
Definition: partial_shape.hpp:98
bool same_scheme(const PartialShape &s) const
Check whether this shape represents the same scheme as the argument.
bool is_static() const
Check if this shape is static.
const_iterator end() const noexcept
Returns a read-only (constant) iterator that points one past the last element in the shape....
Definition: partial_shape.hpp:248
const_iterator cend() const noexcept
Returns a read-only (constant) iterator that points one past the last element in the shape....
Definition: partial_shape.hpp:272
bool is_dynamic() const
Check if this shape is dynamic.
Definition: partial_shape.hpp:94
Shape get_shape() const
Get the unique shape.
const_reverse_iterator rend() const noexcept
Returns a read-only (constant) reverse iterator that points to one before the first element in the sh...
Definition: partial_shape.hpp:264
const Dimension & operator[](size_t i) const
Index operator for PartialShape.
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...
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.
bool relaxes(const PartialShape &s) const
Check whether this shape is a relaxation of the argument.
const_reverse_iterator rbegin() const noexcept
Returns a read-only (constant) reverse iterator that points to the last element in the shape....
Definition: partial_shape.hpp:256
reverse_iterator rend() noexcept
Returns a read/write reverse iterator that points to one before the first element in the shape....
Definition: partial_shape.hpp:260
PartialShape()
Constructs a static PartialShape with zero rank (the shape of a scalar).
friend NGRAPH_API std::ostream & operator<<(std::ostream &str, const PartialShape &shape)
Inserts a human-readable representation of a PartialShape into an output stream.
friend PartialShape operator+(const PartialShape &s1, const PartialShape &s2)
Elementwise addition of two PartialShape objects.
const_reverse_iterator crbegin() const noexcept
Returns a read-only (constant) reverse iterator that points to the last element in the shape....
Definition: partial_shape.hpp:276
bool compatible(const PartialShape &s) const
Check whether this shape is compatible with the argument, i.e., whether it is possible to merge them.
PartialShape(std::initializer_list< Dimension > init)
Constructs a shape with static rank from an initializer list of Dimension.
const_iterator begin() const noexcept
Returns a read-only (constant) iterator that points to the first element in the shape....
Definition: partial_shape.hpp:240
Dimension & operator[](size_t i)
Index operator for PartialShape.
Shape get_max_shape() const
Get the max bounding shape.
iterator end() noexcept
Returns a read/write iterator that points one past the last element in the shape. Iteration is done i...
Definition: partial_shape.hpp:244
PartialShape(const std::vector< Dimension::value_type > &dimensions)
Constructs a PartialShape with static rank from a vector of dimensions values.
const_reverse_iterator crend() const noexcept
Returns a read-only (constant) reverse iterator that points to one before the first element in the sh...
Definition: partial_shape.hpp:280
Shape get_min_shape() const
Get the min bounding shape.
PartialShape(const std::vector< Dimension > &dimensions)
Constructs a PartialShape with static rank from a vector of Dimension.
bool refines(const PartialShape &s) const
Check whether this shape is a refinement of the argument.
Shape to_shape() const
Convert a static PartialShape to a Shape.
iterator begin() noexcept
Returns a read/write iterator that points to the first element in the shape. Iteration is done in ord...
Definition: partial_shape.hpp:236
static PartialShape dynamic(Rank r=Rank::dynamic())
Construct a PartialShape with the given rank and all dimensions (if any) dynamic.
static bool merge_into(PartialShape &dst, const PartialShape &src)
Try to merge one shape into another.
PartialShape(const Shape &shape)
Constructs a static PartialShape from a Shape.
const_iterator cbegin() const noexcept
Returns a read-only (constant) iterator that points to the first element in the shape....
Definition: partial_shape.hpp:268
reverse_iterator rbegin() noexcept
Returns a read/write reverse iterator that points to the last element in the shape....
Definition: partial_shape.hpp:252
Shape for a tensor.
Definition: shape.hpp:31
Provides access to an attribute of type AT as a value accessor type VAT.
Definition: attribute_adapter.hpp:61
The Intel nGraph C++ API.
Definition: attribute_adapter.hpp:28
Dimension Rank
Alias for Dimension, used when the value represents the number of axes in a shape,...
Definition: rank.hpp:27
PartialShape operator+(const PartialShape &s1, const PartialShape &s2)
Elementwise addition of two PartialShape objects.
Definition: type.hpp:39
Implicit broadcast specification.
Definition: attr_types.hpp:323