diff --git a/compiler/circle-resizer/CMakeLists.txt b/compiler/circle-resizer/CMakeLists.txt index 5f27b90e3f2..df1a6e31ce2 100644 --- a/compiler/circle-resizer/CMakeLists.txt +++ b/compiler/circle-resizer/CMakeLists.txt @@ -1 +1,3 @@ +add_subdirectory(src) add_subdirectory(app) +add_subdirectory(tests) diff --git a/compiler/circle-resizer/include/Dim.h b/compiler/circle-resizer/include/Dim.h new file mode 100644 index 00000000000..22d651c16e6 --- /dev/null +++ b/compiler/circle-resizer/include/Dim.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CIRCLE_RESIZER_DIM_H__ +#define __CIRCLE_RESIZER_DIM_H__ + +#include + +namespace circle_resizer +{ + +/** + * The representation of a single dimension. Note that a dimension can be dynamic. + */ +class Dim +{ +public: + /** + * @brief Initialize single dimension. Note that '-1' means a dynamic dimension. + * + * Exceptions: + * - std::runtime_error if provided dim value is less than -1. + */ + explicit Dim(int32_t dim); + + /** + * @brief Create dynamic dimension. Note that it's equivalent of Dim{-1}. + */ + static Dim dynamic(); + +public: + /** + * @brief Returns true if the dimension is dynamic. Otherwise, return false. + */ + bool is_dynamic() const; + + /** + * @brief Returns value of dimension in int32_t representation. + */ + int32_t value() const; + + /** + * @brief Returns true of the current dimension and the provided rhs are equal. + */ + bool operator==(const Dim &rhs) const; + +private: + // Note that in the future, we might need to support dimension with lower and upper bounds + int32_t _dim_value; +}; + +} // namespace circle_resizer + +#endif // __CIRCLE_RESIZER_DIM_H__ diff --git a/compiler/circle-resizer/include/Shape.h b/compiler/circle-resizer/include/Shape.h new file mode 100644 index 00000000000..5b5d6debdba --- /dev/null +++ b/compiler/circle-resizer/include/Shape.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CIRCLE_RESIZER_SHAPE_H__ +#define __CIRCLE_RESIZER_SHAPE_H__ + +#include "Dim.h" + +#include +#include + +namespace circle_resizer +{ + +/** + * The representation of a single shape. + */ +class Shape +{ +public: + /** + * @brief Initialize shape with initializer list of dims. + */ + Shape(const std::initializer_list &dims); + + /** + * @brief Initialize shape with vector of dims. + */ + Shape(const std::vector &shape_vec); + + /** + * @brief Initialize static shape with initializer list of of uint32_t values. + * + * Exceptions: + * - std::out_of_range if some elements in shape_vec exceed int32_t range. + */ + Shape(const std::initializer_list &shape_vec); + + /** + * @brief Create scalar shape. Note, that the same can be achieved with Shape{}. + */ + static Shape scalar(); + +public: + /** + * @brief Returns number of dimensions in the shape. + */ + size_t rank() const; + + /** + * @brief Returns dimension of the position determined by axis. + * + * Exceptions: + * - std::invalid_argument if the method is called on a scalar shape. + * - std::out_of_range if the provided axis is greater than rank. + */ + Dim operator[](const size_t &axis) const; + + /** + * @brief Returns true if the shape is a scalar. Otherwise, return false. + */ + bool is_scalar() const; + + /** + * @brief Returns true if all dimensions in the shape are static or the shape is a scalar. + * Otherwise, return false. + */ + bool is_dynamic() const; + + /** + * @brief Returns true of the current shape and the provided rhs are equal. + */ + bool operator==(const Shape &rhs) const; + + /** + * @brief Print the shape in format [1, 2, 3]. + */ + friend std::ostream &operator<<(std::ostream &os, const Shape &shape); + +private: + std::vector _dims; +}; + +} // namespace circle_resizer + +#endif // __CIRCLE_RESIZER_SHAPE_H__ diff --git a/compiler/circle-resizer/src/CMakeLists.txt b/compiler/circle-resizer/src/CMakeLists.txt new file mode 100644 index 00000000000..e2b397b16f6 --- /dev/null +++ b/compiler/circle-resizer/src/CMakeLists.txt @@ -0,0 +1,8 @@ +list(APPEND CIRCLE_RESIZER_CORE_SOURCES Dim.cpp) +list(APPEND CIRCLE_RESIZER_CORE_SOURCES Shape.cpp) + +add_library(circle_resizer_core STATIC "${CIRCLE_RESIZER_CORE_SOURCES}") + +target_include_directories(circle_resizer_core PUBLIC ../include) + +install(TARGETS circle_resizer_core DESTINATION lib) diff --git a/compiler/circle-resizer/src/Dim.cpp b/compiler/circle-resizer/src/Dim.cpp new file mode 100644 index 00000000000..676dd9d0036 --- /dev/null +++ b/compiler/circle-resizer/src/Dim.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Dim.h" + +#include + +using namespace circle_resizer; + +Dim::Dim(int32_t dim) : _dim_value{dim} +{ + if (dim < -1) + { + throw std::runtime_error("Invalid value of dimension: " + dim); + } +} + +Dim Dim::dynamic() { return Dim{-1}; } + +bool Dim::is_dynamic() const { return _dim_value == -1; } + +int32_t Dim::value() const { return _dim_value; } + +bool Dim::operator==(const Dim &rhs) const { return value() == rhs.value(); } diff --git a/compiler/circle-resizer/src/Shape.cpp b/compiler/circle-resizer/src/Shape.cpp new file mode 100644 index 00000000000..e5c129af771 --- /dev/null +++ b/compiler/circle-resizer/src/Shape.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Shape.h" + +#include +#include + +using namespace circle_resizer; + +Shape::Shape(const std::initializer_list &dims) : _dims{dims} {} + +Shape::Shape(const std::vector &shape_vec) : _dims{shape_vec} {} + +Shape::Shape(const std::initializer_list &shape_vec) +{ + for (const auto &dim : shape_vec) + { + if (dim >= std::numeric_limits::max()) + { + std::out_of_range("Provided dimension: " + std::to_string(dim) + " is out of range"); + } + _dims.emplace_back(Dim{static_cast(dim)}); + } +} + +Shape Shape::scalar() { return Shape{std::initializer_list{}}; } + +size_t Shape::rank() const { return _dims.size(); } + +Dim Shape::operator[](const size_t &axis) const +{ + if (is_scalar()) + { + throw std::invalid_argument("You cannot gather dimension from a scalar"); + } + if (axis > rank() - 1) + { + throw std::out_of_range("Axis=" + std::to_string(axis) + + " is out of range of shape's rank: " + std::to_string(rank())); + } + return _dims[axis]; +} + +bool Shape::is_scalar() const { return _dims.empty(); } + +bool Shape::is_dynamic() const +{ + if (is_scalar()) + { + return false; + } + return std::any_of(std::begin(_dims), std::end(_dims), + [](const Dim &dim) { return dim.is_dynamic(); }); +} + +bool Shape::operator==(const Shape &rhs) const +{ + if (rank() != rhs.rank()) + { + return false; + } + for (size_t axis = 0; axis < rank(); ++axis) + { + if (_dims[axis].value() != rhs[axis].value()) + { + return false; + } + } + return true; +} + +std::ostream &circle_resizer::operator<<(std::ostream &os, const Shape &shape) +{ + if (shape.is_scalar()) + { + os << "[]"; + return os; + } + os << "["; + for (int i = 0; i < shape.rank() - 1; ++i) + { + os << shape[i].value() << ", "; + } + os << shape[shape.rank() - 1].value() << "]"; + return os; +} diff --git a/compiler/circle-resizer/tests/CMakeLists.txt b/compiler/circle-resizer/tests/CMakeLists.txt new file mode 100644 index 00000000000..b95c5195802 --- /dev/null +++ b/compiler/circle-resizer/tests/CMakeLists.txt @@ -0,0 +1,9 @@ +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +list(APPEND CIRCLE_RESIZER_TEST_SOURCES Shape.test.cpp) + +nnas_find_package(GTest REQUIRED) +GTest_AddTest(circle_resizer_unit_test ${CIRCLE_RESIZER_TEST_SOURCES}) +target_link_libraries(circle_resizer_unit_test circle_resizer_core) diff --git a/compiler/circle-resizer/tests/Shape.test.cpp b/compiler/circle-resizer/tests/Shape.test.cpp new file mode 100644 index 00000000000..795bec01690 --- /dev/null +++ b/compiler/circle-resizer/tests/Shape.test.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co., Ltd. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Shape.h" + +#include + +using namespace circle_resizer; + +TEST(ShapeTest, create_scalar) +{ + auto const scalar = Shape::scalar(); + EXPECT_TRUE(scalar.is_scalar()); + EXPECT_EQ(scalar.rank(), 0); +} + +TEST(ShapeTest, gather_scalar_NEG) +{ + auto const scalar = Shape::scalar(); + EXPECT_THROW(scalar[0], std::invalid_argument); +} + +TEST(ShapeTest, is_dynamic) +{ + EXPECT_FALSE(Shape::scalar().is_dynamic()); + + const auto shape1 = Shape{1, 2}; + EXPECT_FALSE(shape1.is_dynamic()); + + const auto shape2 = Shape{Dim{1}, Dim::dynamic()}; + EXPECT_TRUE(shape2.is_dynamic()); + + const auto shape3 = Shape{Dim::dynamic(), Dim::dynamic()}; + EXPECT_TRUE(shape3.is_dynamic()); + + const auto shape4 = Shape{2}; + EXPECT_FALSE(shape4.is_dynamic()); +} + +TEST(ShapeTest, index_operator_NEG) +{ + const auto shape = Shape{Dim{1}, Dim::dynamic()}; + EXPECT_THROW(shape[2], std::out_of_range); +} + +TEST(ShapeTest, equal_operator) +{ + // static vs static with other rank + auto shape1 = Shape{1, 2, 3}; + auto shape2 = Shape{1, 2}; + EXPECT_FALSE(shape1 == shape2); + + // different static vs static + shape1 = Shape{1, 2, 3}; + shape2 = Shape{1, 3, 3}; + EXPECT_FALSE(shape1 == shape2); + + // the same dynamic vs dynamic + shape1 = Shape{Dim::dynamic(), Dim::dynamic()}; + shape2 = Shape{Dim::dynamic(), Dim::dynamic()}; + EXPECT_TRUE(shape1 == shape2); + + shape1 = Shape{Dim::dynamic(), Dim{2}}; + shape2 = Shape{Dim::dynamic(), Dim{2}}; + EXPECT_TRUE(shape1 == shape2); + + // different dynamic vs dynamic + shape1 = Shape{Dim::dynamic(), Dim::dynamic()}; + shape2 = Shape{Dim::dynamic(), Dim{2}}; + EXPECT_FALSE(shape1 == shape2); + + // static vs dynamic + shape1 = Shape{Dim::dynamic(), Dim::dynamic()}; + shape2 = Shape{1, 2}; + EXPECT_FALSE(shape1 == shape2); + + // scalar vs scalar + shape1 = Shape::scalar(); + shape2 = Shape::scalar(); + EXPECT_TRUE(shape1 == shape2); + + // scalar vs static + shape1 = Shape{1}; + shape2 = Shape::scalar(); + EXPECT_FALSE(shape1 == shape2); +} + +TEST(ShapeTest, print) +{ + { // scalar + auto shape = Shape::scalar(); + std::stringstream ss; + ss << shape; + EXPECT_EQ(ss.str(), "[]"); + } + + { // 1D + auto shape = Shape{1}; + std::stringstream ss; + ss << shape; + EXPECT_EQ(ss.str(), "[1]"); + } + + { // static + auto shape = Shape{1, 2, 3}; + std::stringstream ss; + ss << shape; + EXPECT_EQ(ss.str(), "[1, 2, 3]"); + } + + { // dynamic + auto shape = Shape{Dim{1}, Dim::dynamic(), Dim{3}}; + std::stringstream ss; + ss << shape; + EXPECT_EQ(ss.str(), "[1, -1, 3]"); + } + + { // all dimensions dynamic + auto shape = Shape{Dim::dynamic(), Dim::dynamic()}; + std::stringstream ss; + ss << shape; + EXPECT_EQ(ss.str(), "[-1, -1]"); + } +}