From 67dc52e4cfa9606d7c20969c643d05f64e5fba62 Mon Sep 17 00:00:00 2001 From: Mateusz Bencer Date: Tue, 8 Apr 2025 13:55:09 +0200 Subject: [PATCH 1/8] [circle-resizer] Add Shape parser This commit adds the shapes representation and capabilities to parse string inputs into Shapes objects. ONE-DCO-1.0-Signed-off-by: Mateusz Bencer m.bencer@partner.samsung.com --- compiler/circle-resizer/CMakeLists.txt | 2 + compiler/circle-resizer/include/Shape.h | 75 +++++++++++++++++ compiler/circle-resizer/include/ShapeParser.h | 40 +++++++++ compiler/circle-resizer/src/CMakeLists.txt | 8 ++ compiler/circle-resizer/src/Shape.cpp | 35 ++++++++ compiler/circle-resizer/src/ShapeParser.cpp | 83 +++++++++++++++++++ compiler/circle-resizer/tests/CMakeLists.txt | 9 ++ .../circle-resizer/tests/ShapeParser.test.cpp | 73 ++++++++++++++++ 8 files changed, 325 insertions(+) create mode 100644 compiler/circle-resizer/include/Shape.h create mode 100644 compiler/circle-resizer/include/ShapeParser.h create mode 100644 compiler/circle-resizer/src/CMakeLists.txt create mode 100644 compiler/circle-resizer/src/Shape.cpp create mode 100644 compiler/circle-resizer/src/ShapeParser.cpp create mode 100644 compiler/circle-resizer/tests/CMakeLists.txt create mode 100644 compiler/circle-resizer/tests/ShapeParser.test.cpp 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/Shape.h b/compiler/circle-resizer/include/Shape.h new file mode 100644 index 00000000000..55f3408bc81 --- /dev/null +++ b/compiler/circle-resizer/include/Shape.h @@ -0,0 +1,75 @@ +/* + * 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 +#include +#include + +namespace circle_resizer +{ +/** + * The representation of a single dimension. Note that a dimension can be dynamic. +*/ +class Dim +{ +public: + + /** + * @brief Initialize a 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); + +public: + + /** + * @brief Return true if the dimension is dynamic. Otherwise, return false. + */ + bool is_dynamic(); + + /** + * @brief Return value of dimension in int32_t representation. + */ + int32_t value() const; + + /** + * @brief Return 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; +}; + +/** + * The representation of a single shape. +*/ +using Shape = std::vector; + +/** + * The representation of many shapes. +*/ +using Shapes = std::vector; + +} // namespace circle_resizer + +#endif // __CIRCLE_RESIZER_SHAPE_H__ diff --git a/compiler/circle-resizer/include/ShapeParser.h b/compiler/circle-resizer/include/ShapeParser.h new file mode 100644 index 00000000000..b41d281db81 --- /dev/null +++ b/compiler/circle-resizer/include/ShapeParser.h @@ -0,0 +1,40 @@ +/* + * 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_PARSER_H__ +#define __CIRCLE_RESIZER_SHAPE_PARSER_H__ + +#include "Shape.h" + +#include +#include + +namespace circle_resizer +{ +/** + * @brief Parse shapes from string representation to Shapes object. + * + * The single shape is represented by comma-separated integers inside squared brackets. + * If there is more than one shape, they are separated by commas. + * An example for single shape: [1,2,3], an example for many shapes: [1,2,3],[4,5]. + * + * Exceptions: + * std::invalid_argument is the parsing failed. + */ +Shapes parse_shapes(std::string shapes_str); +} // namespace circle_resizer + +#endif // __CIRCLE_RESIZER_SHAPE_PARSER_H__ diff --git a/compiler/circle-resizer/src/CMakeLists.txt b/compiler/circle-resizer/src/CMakeLists.txt new file mode 100644 index 00000000000..17c1c406d37 --- /dev/null +++ b/compiler/circle-resizer/src/CMakeLists.txt @@ -0,0 +1,8 @@ +list(APPEND CIRCLE_RESIZER_CORE_SOURCES Shape.cpp) +list(APPEND CIRCLE_RESIZER_CORE_SOURCES ShapeParser.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/Shape.cpp b/compiler/circle-resizer/src/Shape.cpp new file mode 100644 index 00000000000..91c63c0aa39 --- /dev/null +++ b/compiler/circle-resizer/src/Shape.cpp @@ -0,0 +1,35 @@ +/* + * 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; + +Dim::Dim(int32_t dim) : _dim_value{dim} +{ + if (dim < -1) + { + throw std::runtime_error("Invalid value of dimension: " + dim); + } +} + +bool Dim::is_dynamic() { 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/ShapeParser.cpp b/compiler/circle-resizer/src/ShapeParser.cpp new file mode 100644 index 00000000000..87b076426df --- /dev/null +++ b/compiler/circle-resizer/src/ShapeParser.cpp @@ -0,0 +1,83 @@ +/* + * 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 "ShapeParser.h" + +#include +#include +#include + +using namespace circle_resizer; + +namespace +{ +bool is_blank(const std::string &s) +{ + return !s.empty() && std::find_if(s.begin(), s.end(), + [](unsigned char c) { return !std::isblank(c); }) == s.end(); +} + +Shape parse_single_shape(const std::string &shape_str) +{ + Shape result_shape; + std::stringstream shape_stream(shape_str); + std::string token; + try + { + while (std::getline(shape_stream, token, ',')) + { + result_shape.push_back(Dim{std::stoi(token)}); + } + } + catch (...) + { + throw std::invalid_argument("Error during shape processing: " + shape_str); + } + if (result_shape.empty()) + { + throw std::invalid_argument("No shapes found in input string: " + shape_str); + } + return result_shape; +} +} // namespace + +Shapes circle_resizer::parse_shapes(std::string shapes_str) +{ + Shapes result_shapes; + std::stringstream shapes_stream(shapes_str); + std::string token; + size_t begin_pos = 0, end_pos = 0; + while ((begin_pos = shapes_str.find("[")) != std::string::npos && + (end_pos = shapes_str.find("]")) != std::string::npos) + { + token = shapes_str.substr(begin_pos + 1, end_pos); + result_shapes.push_back(parse_single_shape(token)); + shapes_str.erase(0, end_pos + 1); + } + + if (result_shapes.empty()) + { + throw std::invalid_argument("No shapes found in input string: " + shapes_str); + } + + // the rest of the input not handled by loop above cannot be processed properly + if (shapes_str.size() > 0 && !is_blank(shapes_str)) + { + throw std::invalid_argument("The part of input shape: " + shapes_str + " cannot be processed"); + } + + return result_shapes; +} diff --git a/compiler/circle-resizer/tests/CMakeLists.txt b/compiler/circle-resizer/tests/CMakeLists.txt new file mode 100644 index 00000000000..c57c15b7d41 --- /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 ShapeParser.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/ShapeParser.test.cpp b/compiler/circle-resizer/tests/ShapeParser.test.cpp new file mode 100644 index 00000000000..ea601ea81db --- /dev/null +++ b/compiler/circle-resizer/tests/ShapeParser.test.cpp @@ -0,0 +1,73 @@ +/* + * 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 "ShapeParser.h" + +#include + +#include +#include +#include +#include + +using namespace circle_resizer; + +class ParseShapeTestFixture : public ::testing::TestWithParam> +{ +}; + +TEST_P(ParseShapeTestFixture, successful_parsing) +{ + const auto [input_shape_str, expected_shapes] = GetParam(); + Shapes result_shapes; + EXPECT_NO_THROW(result_shapes = parse_shapes(input_shape_str)); + ASSERT_EQ(result_shapes, expected_shapes); +} + +INSTANTIATE_TEST_SUITE_P( + ParseShapeTest, ParseShapeTestFixture, + ::testing::Values( + // single shape + std::make_tuple("[3,4]", Shapes{Shape{Dim{3}, Dim{4}}}), + std::make_tuple("[3]", Shapes{Shape{Dim{3}}}), std::make_tuple("[-1]", Shapes{Shape{Dim{-1}}}), + std::make_tuple("[ 5, 6]", Shapes{Shape{Dim{5}, Dim{6}}}), + std::make_tuple("[3 , 4]", Shapes{Shape{Dim{3}, Dim{4}}}), + std::make_tuple("[-1 , 4]", Shapes{Shape{Dim{-1}, Dim{4}}}), + // many shapes + std::make_tuple("[3,4],[5,6]", Shapes{Shape{Dim{3}, Dim{4}}, Shape{Dim{5}, Dim{6}}}), + std::make_tuple("[1],[2]", Shapes{Shape{Dim{1}}, Shape{Dim{2}}}), + std::make_tuple(" [3, 4] , [5,6]", Shapes{Shape{Dim{3}, Dim{4}}, Shape{Dim{5}, Dim{6}}}), + std::make_tuple(" [ 1 ] ,[ 2]", Shapes{Shape{Dim{1}}, Shape{Dim{2}}}), + std::make_tuple(" [ 1 ] ,[ 2] ", Shapes{Shape{Dim{1}}, Shape{Dim{2}}}), + std::make_tuple(" [1,2],[3,4,5],[6,7,8,9]", + Shapes{Shape{Dim{1}, Dim{2}}, Shape{Dim{3}, Dim{4}, Dim{5}}, + Shape{Dim{6}, Dim{7}, Dim{8}, Dim{9}}}))); + +class InvalidArgParseShapeTestFixture : public ::testing::TestWithParam +{ +}; + +TEST_P(InvalidArgParseShapeTestFixture, neg_invalid_input) +{ + EXPECT_THROW(parse_shapes(GetParam()), std::invalid_argument); +} + +INSTANTIATE_TEST_SUITE_P(InvalidArgParseShape, InvalidArgParseShapeTestFixture, + ::testing::Values(std::string{""}, std::string{"]"}, std::string{"1a,2"}, + std::string{"[-2]"}, std::string{"[-2,1,3]"}, + std::string{"-1"}, std::string{"7,7"}, std::string{"8"}, + std::string{"[8],9"}, std::string{"1,2"}, + std::string{"[1],[2],"})); From e2a0fb5f9da23ea32fffa53b2b3ef5629ffe7f13 Mon Sep 17 00:00:00 2001 From: Mateusz Bencer Date: Tue, 8 Apr 2025 14:33:40 +0200 Subject: [PATCH 2/8] styles applied --- compiler/circle-resizer/include/Shape.h | 10 ++++------ compiler/circle-resizer/include/ShapeParser.h | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/compiler/circle-resizer/include/Shape.h b/compiler/circle-resizer/include/Shape.h index 55f3408bc81..886d44c5de9 100644 --- a/compiler/circle-resizer/include/Shape.h +++ b/compiler/circle-resizer/include/Shape.h @@ -25,21 +25,19 @@ namespace circle_resizer { /** * The representation of a single dimension. Note that a dimension can be dynamic. -*/ + */ class Dim { public: - /** * @brief Initialize a 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); public: - /** * @brief Return true if the dimension is dynamic. Otherwise, return false. */ @@ -62,12 +60,12 @@ class Dim /** * The representation of a single shape. -*/ + */ using Shape = std::vector; /** * The representation of many shapes. -*/ + */ using Shapes = std::vector; } // namespace circle_resizer diff --git a/compiler/circle-resizer/include/ShapeParser.h b/compiler/circle-resizer/include/ShapeParser.h index b41d281db81..81ce493c083 100644 --- a/compiler/circle-resizer/include/ShapeParser.h +++ b/compiler/circle-resizer/include/ShapeParser.h @@ -26,11 +26,11 @@ namespace circle_resizer { /** * @brief Parse shapes from string representation to Shapes object. - * + * * The single shape is represented by comma-separated integers inside squared brackets. * If there is more than one shape, they are separated by commas. * An example for single shape: [1,2,3], an example for many shapes: [1,2,3],[4,5]. - * + * * Exceptions: * std::invalid_argument is the parsing failed. */ From 48f69214f2f16311c99874fe66a0b1a8501fe4c9 Mon Sep 17 00:00:00 2001 From: Mateusz Bencer Date: Thu, 10 Apr 2025 09:29:10 +0200 Subject: [PATCH 3/8] review remarks --- compiler/circle-resizer/include/ShapeParser.h | 3 +-- compiler/circle-resizer/src/ShapeParser.cpp | 26 +++++++++---------- .../circle-resizer/tests/ShapeParser.test.cpp | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/compiler/circle-resizer/include/ShapeParser.h b/compiler/circle-resizer/include/ShapeParser.h index 81ce493c083..4c0761b57fe 100644 --- a/compiler/circle-resizer/include/ShapeParser.h +++ b/compiler/circle-resizer/include/ShapeParser.h @@ -20,7 +20,6 @@ #include "Shape.h" #include -#include namespace circle_resizer { @@ -34,7 +33,7 @@ namespace circle_resizer * Exceptions: * std::invalid_argument is the parsing failed. */ -Shapes parse_shapes(std::string shapes_str); +Shapes parse_shapes(const std::string &shapes); } // namespace circle_resizer #endif // __CIRCLE_RESIZER_SHAPE_PARSER_H__ diff --git a/compiler/circle-resizer/src/ShapeParser.cpp b/compiler/circle-resizer/src/ShapeParser.cpp index 87b076426df..16a12a9c254 100644 --- a/compiler/circle-resizer/src/ShapeParser.cpp +++ b/compiler/circle-resizer/src/ShapeParser.cpp @@ -30,10 +30,10 @@ bool is_blank(const std::string &s) [](unsigned char c) { return !std::isblank(c); }) == s.end(); } -Shape parse_single_shape(const std::string &shape_str) +Shape parse_single_shape(const std::string &shape) { Shape result_shape; - std::stringstream shape_stream(shape_str); + std::stringstream shape_stream(shape); std::string token; try { @@ -44,39 +44,39 @@ Shape parse_single_shape(const std::string &shape_str) } catch (...) { - throw std::invalid_argument("Error during shape processing: " + shape_str); + throw std::invalid_argument("Error during shape processing: " + shape); } if (result_shape.empty()) { - throw std::invalid_argument("No shapes found in input string: " + shape_str); + throw std::invalid_argument("No shapes found in input string: " + shape); } return result_shape; } } // namespace -Shapes circle_resizer::parse_shapes(std::string shapes_str) +Shapes circle_resizer::parse_shapes(const std::string &shapes) { Shapes result_shapes; - std::stringstream shapes_stream(shapes_str); + auto shapes_tmp = shapes; std::string token; size_t begin_pos = 0, end_pos = 0; - while ((begin_pos = shapes_str.find("[")) != std::string::npos && - (end_pos = shapes_str.find("]")) != std::string::npos) + while ((begin_pos = shapes_tmp.find("[")) != std::string::npos && + (end_pos = shapes_tmp.find("]")) != std::string::npos) { - token = shapes_str.substr(begin_pos + 1, end_pos); + token = shapes_tmp.substr(begin_pos + 1, end_pos); result_shapes.push_back(parse_single_shape(token)); - shapes_str.erase(0, end_pos + 1); + shapes_tmp.erase(0, end_pos + 1); } if (result_shapes.empty()) { - throw std::invalid_argument("No shapes found in input string: " + shapes_str); + throw std::invalid_argument("No shapes found in the input string: " + shapes); } // the rest of the input not handled by loop above cannot be processed properly - if (shapes_str.size() > 0 && !is_blank(shapes_str)) + if (shapes_tmp.size() > 0 && !is_blank(shapes_tmp)) { - throw std::invalid_argument("The part of input shape: " + shapes_str + " cannot be processed"); + throw std::invalid_argument("The part of input shapes: " + shapes_tmp + " cannot be processed"); } return result_shapes; diff --git a/compiler/circle-resizer/tests/ShapeParser.test.cpp b/compiler/circle-resizer/tests/ShapeParser.test.cpp index ea601ea81db..5abbd39a053 100644 --- a/compiler/circle-resizer/tests/ShapeParser.test.cpp +++ b/compiler/circle-resizer/tests/ShapeParser.test.cpp @@ -60,7 +60,7 @@ class InvalidArgParseShapeTestFixture : public ::testing::TestWithParam Date: Thu, 10 Apr 2025 11:06:11 +0200 Subject: [PATCH 4/8] introduce scalars representation --- compiler/circle-resizer/include/Shape.h | 21 +++++++++-- compiler/circle-resizer/src/Shape.cpp | 36 +++++++++++++++---- compiler/circle-resizer/src/ShapeParser.cpp | 12 +++++-- .../circle-resizer/tests/ShapeParser.test.cpp | 9 +++-- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/compiler/circle-resizer/include/Shape.h b/compiler/circle-resizer/include/Shape.h index 886d44c5de9..7443ae9ff3e 100644 --- a/compiler/circle-resizer/include/Shape.h +++ b/compiler/circle-resizer/include/Shape.h @@ -19,6 +19,7 @@ #include #include +#include #include namespace circle_resizer @@ -35,13 +36,24 @@ class Dim * Exceptions: * - std::runtime_error if provided dim value is less than -1. */ - explicit Dim(int32_t dim); + explicit Dim(int32_t dim_value); + + /** + * @brief Initialize a single scalar dimension. Note that a scalar can be used to represent + * a shape with rank = 0 and size = 1. + */ + static Dim scalar(); public: /** * @brief Return true if the dimension is dynamic. Otherwise, return false. */ - bool is_dynamic(); + bool is_dynamic() const; + + /** + * @brief Return true if the dimension is a scalar. Otherwise, return false. + */ + bool is_scalar() const; /** * @brief Return value of dimension in int32_t representation. @@ -53,9 +65,12 @@ class Dim */ bool operator==(const Dim &rhs) const; +private: + explicit Dim(const std::optional &dim); + private: // Note that in the future, we might need to support dimension with lower and upper bounds - int32_t _dim_value; + std::optional _dim; }; /** diff --git a/compiler/circle-resizer/src/Shape.cpp b/compiler/circle-resizer/src/Shape.cpp index 91c63c0aa39..858fec62e30 100644 --- a/compiler/circle-resizer/src/Shape.cpp +++ b/compiler/circle-resizer/src/Shape.cpp @@ -20,16 +20,40 @@ using namespace circle_resizer; -Dim::Dim(int32_t dim) : _dim_value{dim} +Dim::Dim(const std::optional &dim) : _dim{dim} {} + +Dim::Dim(int32_t dim_value) : Dim{std::optional{dim_value}} { - if (dim < -1) + if (_dim.value() < -1) { - throw std::runtime_error("Invalid value of dimension: " + dim); + throw std::runtime_error("Invalid value of dimension: " + _dim.value()); } } -bool Dim::is_dynamic() { return _dim_value == -1; } +Dim Dim::scalar() { return Dim{std::nullopt}; } + +bool Dim::is_scalar() const { return !_dim.has_value(); } -int32_t Dim::value() const { return _dim_value; } +bool Dim::is_dynamic() const { return _dim.value() == -1; } + +int32_t Dim::value() const +{ + if (!_dim.has_value()) + { + std::runtime_error("The dimension is a scalar"); + } + return _dim.value(); +} -bool Dim::operator==(const Dim &rhs) const { return value() == rhs.value(); } +bool Dim::operator==(const Dim &rhs) const +{ + if (is_scalar() && rhs.is_scalar()) + { + return true; + } + if (is_scalar() != rhs.is_scalar()) + { + return false; + } + return value() == rhs.value(); +} diff --git a/compiler/circle-resizer/src/ShapeParser.cpp b/compiler/circle-resizer/src/ShapeParser.cpp index 16a12a9c254..53a1db4c4b9 100644 --- a/compiler/circle-resizer/src/ShapeParser.cpp +++ b/compiler/circle-resizer/src/ShapeParser.cpp @@ -32,6 +32,11 @@ bool is_blank(const std::string &s) Shape parse_single_shape(const std::string &shape) { + if (shape.empty() || is_blank(shape)) + { + return Shape{Dim::scalar()}; + } + Shape result_shape; std::stringstream shape_stream(shape); std::string token; @@ -60,10 +65,11 @@ Shapes circle_resizer::parse_shapes(const std::string &shapes) auto shapes_tmp = shapes; std::string token; size_t begin_pos = 0, end_pos = 0; - while ((begin_pos = shapes_tmp.find("[")) != std::string::npos && - (end_pos = shapes_tmp.find("]")) != std::string::npos) + while ((begin_pos = shapes_tmp.find_first_of("[")) != std::string::npos && + (end_pos = shapes_tmp.find_first_of("]")) != std::string::npos) { - token = shapes_tmp.substr(begin_pos + 1, end_pos); + const size_t token_size = end_pos - begin_pos - 1; + token = shapes_tmp.substr(begin_pos + 1, token_size); result_shapes.push_back(parse_single_shape(token)); shapes_tmp.erase(0, end_pos + 1); } diff --git a/compiler/circle-resizer/tests/ShapeParser.test.cpp b/compiler/circle-resizer/tests/ShapeParser.test.cpp index 5abbd39a053..fe7b66cbad1 100644 --- a/compiler/circle-resizer/tests/ShapeParser.test.cpp +++ b/compiler/circle-resizer/tests/ShapeParser.test.cpp @@ -54,7 +54,12 @@ INSTANTIATE_TEST_SUITE_P( std::make_tuple(" [ 1 ] ,[ 2] ", Shapes{Shape{Dim{1}}, Shape{Dim{2}}}), std::make_tuple(" [1,2],[3,4,5],[6,7,8,9]", Shapes{Shape{Dim{1}, Dim{2}}, Shape{Dim{3}, Dim{4}, Dim{5}}, - Shape{Dim{6}, Dim{7}, Dim{8}, Dim{9}}}))); + Shape{Dim{6}, Dim{7}, Dim{8}, Dim{9}}}), + // scalars + std::make_tuple("[]", Shapes{Shape{Dim::scalar()}}), + std::make_tuple("[],[]", Shapes{Shape{Dim::scalar()}, Shape{Dim::scalar()}}), + std::make_tuple("[],[2]", Shapes{Shape{Dim::scalar()}, Shape{Dim{2}}}), + std::make_tuple("[ ]", Shapes{Shape{Dim::scalar()}}))); class InvalidArgParseShapeTestFixture : public ::testing::TestWithParam { @@ -70,4 +75,4 @@ INSTANTIATE_TEST_SUITE_P(InvalidArgParseShape, InvalidArgParseShapeTestFixture, std::string{"[-2]"}, std::string{"[-2,1,3]"}, std::string{"-1"}, std::string{"7,7"}, std::string{"8"}, std::string{"[8],9"}, std::string{"1,2"}, - std::string{"[1],[2],"})); + std::string{"[1],[2],"}, std::string{"[[]],"})); From 067ff64fb27a8e56b57cd8ad26951cbf4feda656 Mon Sep 17 00:00:00 2001 From: Mateusz Bencer Date: Thu, 10 Apr 2025 23:23:49 +0200 Subject: [PATCH 5/8] introduce a separate shape class --- compiler/circle-resizer/include/Dim.h | 60 +++++++++++++++++ compiler/circle-resizer/include/Shape.h | 65 ++++--------------- compiler/circle-resizer/include/ShapeParser.h | 3 +- compiler/circle-resizer/src/CMakeLists.txt | 1 + compiler/circle-resizer/src/Dim.cpp | 36 ++++++++++ compiler/circle-resizer/src/Shape.cpp | 58 +++++++++++------ compiler/circle-resizer/src/ShapeParser.cpp | 15 +++-- .../circle-resizer/tests/ShapeParser.test.cpp | 9 +-- 8 files changed, 163 insertions(+), 84 deletions(-) create mode 100644 compiler/circle-resizer/include/Dim.h create mode 100644 compiler/circle-resizer/src/Dim.cpp diff --git a/compiler/circle-resizer/include/Dim.h b/compiler/circle-resizer/include/Dim.h new file mode 100644 index 00000000000..82c8c855c43 --- /dev/null +++ b/compiler/circle-resizer/include/Dim.h @@ -0,0 +1,60 @@ +/* + * 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 a 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); + + public: + /** + * @brief Return true if the dimension is dynamic. Otherwise, return false. + */ + bool is_dynamic() const; + + /** + * @brief Return value of dimension in int32_t representation. + */ + int32_t value() const; + + /** + * @brief Return 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 index 7443ae9ff3e..364200245d8 100644 --- a/compiler/circle-resizer/include/Shape.h +++ b/compiler/circle-resizer/include/Shape.h @@ -17,72 +17,35 @@ #ifndef __CIRCLE_RESIZER_SHAPE_H__ #define __CIRCLE_RESIZER_SHAPE_H__ -#include -#include -#include +#include "Dim.h" + +#include #include namespace circle_resizer { /** - * The representation of a single dimension. Note that a dimension can be dynamic. + * The representation of a single shape. */ -class Dim +class Shape { public: - /** - * @brief Initialize a 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_value); - - /** - * @brief Initialize a single scalar dimension. Note that a scalar can be used to represent - * a shape with rank = 0 and size = 1. - */ - static Dim scalar(); + Shape(const std::initializer_list &dims); + Shape(const std::vector &shape_vec); + static Shape scalar(); public: - /** - * @brief Return true if the dimension is dynamic. Otherwise, return false. - */ - bool is_dynamic() const; - - /** - * @brief Return true if the dimension is a scalar. Otherwise, return false. - */ + size_t rank() const; + Dim operator[](const size_t &axis) const; bool is_scalar() const; - - /** - * @brief Return value of dimension in int32_t representation. - */ - int32_t value() const; - - /** - * @brief Return true of the current dimension and the provided rhs are equal. - */ - bool operator==(const Dim &rhs) const; - -private: - explicit Dim(const std::optional &dim); + bool is_dynamic() const; + bool operator==(const Shape &rhs) const; + friend std::ostream &operator<<(std::ostream &os, const Shape &shape); private: - // Note that in the future, we might need to support dimension with lower and upper bounds - std::optional _dim; + std::vector _dims; }; -/** - * The representation of a single shape. - */ -using Shape = std::vector; - -/** - * The representation of many shapes. - */ -using Shapes = std::vector; - } // namespace circle_resizer #endif // __CIRCLE_RESIZER_SHAPE_H__ diff --git a/compiler/circle-resizer/include/ShapeParser.h b/compiler/circle-resizer/include/ShapeParser.h index 4c0761b57fe..fcdc631f6be 100644 --- a/compiler/circle-resizer/include/ShapeParser.h +++ b/compiler/circle-resizer/include/ShapeParser.h @@ -20,6 +20,7 @@ #include "Shape.h" #include +#include namespace circle_resizer { @@ -33,7 +34,7 @@ namespace circle_resizer * Exceptions: * std::invalid_argument is the parsing failed. */ -Shapes parse_shapes(const std::string &shapes); +std::vector parse_shapes(const std::string &shapes); } // namespace circle_resizer #endif // __CIRCLE_RESIZER_SHAPE_PARSER_H__ diff --git a/compiler/circle-resizer/src/CMakeLists.txt b/compiler/circle-resizer/src/CMakeLists.txt index 17c1c406d37..03f5a2dba14 100644 --- a/compiler/circle-resizer/src/CMakeLists.txt +++ b/compiler/circle-resizer/src/CMakeLists.txt @@ -1,3 +1,4 @@ +list(APPEND CIRCLE_RESIZER_CORE_SOURCES Dim.cpp) list(APPEND CIRCLE_RESIZER_CORE_SOURCES Shape.cpp) list(APPEND CIRCLE_RESIZER_CORE_SOURCES ShapeParser.cpp) diff --git a/compiler/circle-resizer/src/Dim.cpp b/compiler/circle-resizer/src/Dim.cpp new file mode 100644 index 00000000000..4bb9eb6aee7 --- /dev/null +++ b/compiler/circle-resizer/src/Dim.cpp @@ -0,0 +1,36 @@ +/* + * 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); + } + } + + 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(); } + \ No newline at end of file diff --git a/compiler/circle-resizer/src/Shape.cpp b/compiler/circle-resizer/src/Shape.cpp index 858fec62e30..9cf7359c7c5 100644 --- a/compiler/circle-resizer/src/Shape.cpp +++ b/compiler/circle-resizer/src/Shape.cpp @@ -16,44 +16,60 @@ #include "Shape.h" -#include +#include using namespace circle_resizer; -Dim::Dim(const std::optional &dim) : _dim{dim} {} +Shape::Shape(const std::initializer_list &dims) : _dims{dims} {} -Dim::Dim(int32_t dim_value) : Dim{std::optional{dim_value}} +Shape::Shape(const std::vector &shape_vec) : _dims{shape_vec} {} + +Shape Shape::scalar() { return Shape{std::initializer_list{}}; } + +size_t Shape::rank() const { return _dims.size(); } + +Dim Shape::operator[](const size_t &axis) const { return _dims[axis]; } + +bool Shape::is_scalar() const { return _dims.empty(); } + +bool Shape::is_dynamic() const { - if (_dim.value() < -1) + if (is_scalar()) { - throw std::runtime_error("Invalid value of dimension: " + _dim.value()); + return false; } + return std::any_of(std::begin(_dims), std::end(_dims), + [](const Dim &dim) { return dim.is_dynamic(); }); } -Dim Dim::scalar() { return Dim{std::nullopt}; } - -bool Dim::is_scalar() const { return !_dim.has_value(); } - -bool Dim::is_dynamic() const { return _dim.value() == -1; } - -int32_t Dim::value() const +bool Shape::operator==(const Shape &rhs) const { - if (!_dim.has_value()) + if (rank() != rhs.rank()) + { + return false; + } + for (size_t axis = 0; axis < rank(); ++axis) { - std::runtime_error("The dimension is a scalar"); + if (_dims[axis].value() != rhs[axis].value()) + { + return false; + } } - return _dim.value(); + return true; } -bool Dim::operator==(const Dim &rhs) const +std::ostream &circle_resizer::operator<<(std::ostream &os, const Shape &shape) { - if (is_scalar() && rhs.is_scalar()) + if (shape.is_scalar()) { - return true; + os << "[]"; + return os; } - if (is_scalar() != rhs.is_scalar()) + os << "["; + for (int i = 0; i < shape.rank() - 1; ++i) { - return false; + os << shape[i].value() << ", "; } - return value() == rhs.value(); + os << shape[shape.rank() - 1].value() << "]"; + return os; } diff --git a/compiler/circle-resizer/src/ShapeParser.cpp b/compiler/circle-resizer/src/ShapeParser.cpp index 53a1db4c4b9..94edb2e4623 100644 --- a/compiler/circle-resizer/src/ShapeParser.cpp +++ b/compiler/circle-resizer/src/ShapeParser.cpp @@ -15,6 +15,7 @@ */ #include "ShapeParser.h" +#include #include #include @@ -34,34 +35,34 @@ Shape parse_single_shape(const std::string &shape) { if (shape.empty() || is_blank(shape)) { - return Shape{Dim::scalar()}; + return Shape::scalar(); } - Shape result_shape; + std::vector result_dims; std::stringstream shape_stream(shape); std::string token; try { while (std::getline(shape_stream, token, ',')) { - result_shape.push_back(Dim{std::stoi(token)}); + result_dims.push_back(Dim{std::stoi(token)}); } } catch (...) { throw std::invalid_argument("Error during shape processing: " + shape); } - if (result_shape.empty()) + if (result_dims.empty()) { throw std::invalid_argument("No shapes found in input string: " + shape); } - return result_shape; + return Shape{result_dims}; } } // namespace -Shapes circle_resizer::parse_shapes(const std::string &shapes) +std::vector circle_resizer::parse_shapes(const std::string &shapes) { - Shapes result_shapes; + std::vector result_shapes; auto shapes_tmp = shapes; std::string token; size_t begin_pos = 0, end_pos = 0; diff --git a/compiler/circle-resizer/tests/ShapeParser.test.cpp b/compiler/circle-resizer/tests/ShapeParser.test.cpp index fe7b66cbad1..2203b71273f 100644 --- a/compiler/circle-resizer/tests/ShapeParser.test.cpp +++ b/compiler/circle-resizer/tests/ShapeParser.test.cpp @@ -24,6 +24,7 @@ #include using namespace circle_resizer; +using Shapes = std::vector; class ParseShapeTestFixture : public ::testing::TestWithParam> { @@ -56,10 +57,10 @@ INSTANTIATE_TEST_SUITE_P( Shapes{Shape{Dim{1}, Dim{2}}, Shape{Dim{3}, Dim{4}, Dim{5}}, Shape{Dim{6}, Dim{7}, Dim{8}, Dim{9}}}), // scalars - std::make_tuple("[]", Shapes{Shape{Dim::scalar()}}), - std::make_tuple("[],[]", Shapes{Shape{Dim::scalar()}, Shape{Dim::scalar()}}), - std::make_tuple("[],[2]", Shapes{Shape{Dim::scalar()}, Shape{Dim{2}}}), - std::make_tuple("[ ]", Shapes{Shape{Dim::scalar()}}))); + std::make_tuple("[]", Shapes{Shape::scalar()}), + std::make_tuple("[],[]", Shapes{Shape::scalar(), Shape::scalar()}), + std::make_tuple("[],[2]", Shapes{Shape::scalar(), Shape{Dim{2}}}), + std::make_tuple("[ ]", Shapes{Shape::scalar()}))); class InvalidArgParseShapeTestFixture : public ::testing::TestWithParam { From 2df5634abcd704f63794ecef69e0fbc948b07fb7 Mon Sep 17 00:00:00 2001 From: Mateusz Bencer Date: Thu, 10 Apr 2025 23:31:50 +0200 Subject: [PATCH 6/8] styles applied --- compiler/circle-resizer/include/Dim.h | 82 +++++++++++++-------------- compiler/circle-resizer/src/Dim.cpp | 37 ++++++------ 2 files changed, 59 insertions(+), 60 deletions(-) diff --git a/compiler/circle-resizer/include/Dim.h b/compiler/circle-resizer/include/Dim.h index 82c8c855c43..09ecc26b7e1 100644 --- a/compiler/circle-resizer/include/Dim.h +++ b/compiler/circle-resizer/include/Dim.h @@ -14,47 +14,47 @@ * 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 a 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); - - public: - /** - * @brief Return true if the dimension is dynamic. Otherwise, return false. - */ - bool is_dynamic() const; - - /** - * @brief Return value of dimension in int32_t representation. - */ - int32_t value() const; - - /** - * @brief Return 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; - }; +#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 a 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); + +public: + /** + * @brief Return true if the dimension is dynamic. Otherwise, return false. + */ + bool is_dynamic() const; + + /** + * @brief Return value of dimension in int32_t representation. + */ + int32_t value() const; + + /** + * @brief Return 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/src/Dim.cpp b/compiler/circle-resizer/src/Dim.cpp index 4bb9eb6aee7..2ddc1e54729 100644 --- a/compiler/circle-resizer/src/Dim.cpp +++ b/compiler/circle-resizer/src/Dim.cpp @@ -14,23 +14,22 @@ * limitations under the License. */ - #include "Dim.h" +#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); - } - } - - 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(); } - \ No newline at end of file +#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); + } +} + +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(); } From ce0119c922b0a51dd757da6f4b5bb176d7c37ea2 Mon Sep 17 00:00:00 2001 From: Mateusz Bencer Date: Fri, 11 Apr 2025 14:12:59 +0200 Subject: [PATCH 7/8] unit tests + docs --- compiler/circle-resizer/include/Dim.h | 13 +- compiler/circle-resizer/include/Shape.h | 47 ++++++ compiler/circle-resizer/include/ShapeParser.h | 2 +- compiler/circle-resizer/src/Dim.cpp | 2 + compiler/circle-resizer/src/Shape.cpp | 27 +++- compiler/circle-resizer/tests/CMakeLists.txt | 1 + compiler/circle-resizer/tests/Shape.test.cpp | 137 ++++++++++++++++++ .../circle-resizer/tests/ShapeParser.test.cpp | 45 +++--- 8 files changed, 245 insertions(+), 29 deletions(-) create mode 100644 compiler/circle-resizer/tests/Shape.test.cpp diff --git a/compiler/circle-resizer/include/Dim.h b/compiler/circle-resizer/include/Dim.h index 09ecc26b7e1..615baaf0cee 100644 --- a/compiler/circle-resizer/include/Dim.h +++ b/compiler/circle-resizer/include/Dim.h @@ -28,26 +28,31 @@ class Dim { public: /** - * @brief Initialize a single dimension. Note that '-1' means a dynamic dimension. + * @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 Return true if the dimension is dynamic. Otherwise, return false. + * @brief Returns true if the dimension is dynamic. Otherwise, return false. */ bool is_dynamic() const; /** - * @brief Return value of dimension in int32_t representation. + * @brief Returns value of dimension in int32_t representation. */ int32_t value() const; /** - * @brief Return true of the current dimension and the provided rhs are equal. + * @brief Returns true of the current dimension and the provided rhs are equal. */ bool operator==(const Dim &rhs) const; diff --git a/compiler/circle-resizer/include/Shape.h b/compiler/circle-resizer/include/Shape.h index 364200245d8..f935c6187c4 100644 --- a/compiler/circle-resizer/include/Shape.h +++ b/compiler/circle-resizer/include/Shape.h @@ -30,16 +30,63 @@ namespace circle_resizer 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: diff --git a/compiler/circle-resizer/include/ShapeParser.h b/compiler/circle-resizer/include/ShapeParser.h index fcdc631f6be..7da8baef7e5 100644 --- a/compiler/circle-resizer/include/ShapeParser.h +++ b/compiler/circle-resizer/include/ShapeParser.h @@ -32,7 +32,7 @@ namespace circle_resizer * An example for single shape: [1,2,3], an example for many shapes: [1,2,3],[4,5]. * * Exceptions: - * std::invalid_argument is the parsing failed. + * std::invalid_argument if the parsing failed. */ std::vector parse_shapes(const std::string &shapes); } // namespace circle_resizer diff --git a/compiler/circle-resizer/src/Dim.cpp b/compiler/circle-resizer/src/Dim.cpp index 2ddc1e54729..676dd9d0036 100644 --- a/compiler/circle-resizer/src/Dim.cpp +++ b/compiler/circle-resizer/src/Dim.cpp @@ -28,6 +28,8 @@ Dim::Dim(int32_t dim) : _dim_value{dim} } } +Dim Dim::dynamic() { return Dim{-1}; } + bool Dim::is_dynamic() const { return _dim_value == -1; } int32_t Dim::value() const { return _dim_value; } diff --git a/compiler/circle-resizer/src/Shape.cpp b/compiler/circle-resizer/src/Shape.cpp index 9cf7359c7c5..e5c129af771 100644 --- a/compiler/circle-resizer/src/Shape.cpp +++ b/compiler/circle-resizer/src/Shape.cpp @@ -17,6 +17,7 @@ #include "Shape.h" #include +#include using namespace circle_resizer; @@ -24,11 +25,35 @@ 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 { return _dims[axis]; } +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(); } diff --git a/compiler/circle-resizer/tests/CMakeLists.txt b/compiler/circle-resizer/tests/CMakeLists.txt index c57c15b7d41..6f450467537 100644 --- a/compiler/circle-resizer/tests/CMakeLists.txt +++ b/compiler/circle-resizer/tests/CMakeLists.txt @@ -2,6 +2,7 @@ if(NOT ENABLE_TEST) return() endif(NOT ENABLE_TEST) +list(APPEND CIRCLE_RESIZER_TEST_SOURCES Shape.test.cpp) list(APPEND CIRCLE_RESIZER_TEST_SOURCES ShapeParser.test.cpp) nnas_find_package(GTest REQUIRED) 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]"); + } +} diff --git a/compiler/circle-resizer/tests/ShapeParser.test.cpp b/compiler/circle-resizer/tests/ShapeParser.test.cpp index 2203b71273f..1473ff954bd 100644 --- a/compiler/circle-resizer/tests/ShapeParser.test.cpp +++ b/compiler/circle-resizer/tests/ShapeParser.test.cpp @@ -38,29 +38,28 @@ TEST_P(ParseShapeTestFixture, successful_parsing) ASSERT_EQ(result_shapes, expected_shapes); } -INSTANTIATE_TEST_SUITE_P( - ParseShapeTest, ParseShapeTestFixture, - ::testing::Values( - // single shape - std::make_tuple("[3,4]", Shapes{Shape{Dim{3}, Dim{4}}}), - std::make_tuple("[3]", Shapes{Shape{Dim{3}}}), std::make_tuple("[-1]", Shapes{Shape{Dim{-1}}}), - std::make_tuple("[ 5, 6]", Shapes{Shape{Dim{5}, Dim{6}}}), - std::make_tuple("[3 , 4]", Shapes{Shape{Dim{3}, Dim{4}}}), - std::make_tuple("[-1 , 4]", Shapes{Shape{Dim{-1}, Dim{4}}}), - // many shapes - std::make_tuple("[3,4],[5,6]", Shapes{Shape{Dim{3}, Dim{4}}, Shape{Dim{5}, Dim{6}}}), - std::make_tuple("[1],[2]", Shapes{Shape{Dim{1}}, Shape{Dim{2}}}), - std::make_tuple(" [3, 4] , [5,6]", Shapes{Shape{Dim{3}, Dim{4}}, Shape{Dim{5}, Dim{6}}}), - std::make_tuple(" [ 1 ] ,[ 2]", Shapes{Shape{Dim{1}}, Shape{Dim{2}}}), - std::make_tuple(" [ 1 ] ,[ 2] ", Shapes{Shape{Dim{1}}, Shape{Dim{2}}}), - std::make_tuple(" [1,2],[3,4,5],[6,7,8,9]", - Shapes{Shape{Dim{1}, Dim{2}}, Shape{Dim{3}, Dim{4}, Dim{5}}, - Shape{Dim{6}, Dim{7}, Dim{8}, Dim{9}}}), - // scalars - std::make_tuple("[]", Shapes{Shape::scalar()}), - std::make_tuple("[],[]", Shapes{Shape::scalar(), Shape::scalar()}), - std::make_tuple("[],[2]", Shapes{Shape::scalar(), Shape{Dim{2}}}), - std::make_tuple("[ ]", Shapes{Shape::scalar()}))); +INSTANTIATE_TEST_SUITE_P(ParseShapeTest, ParseShapeTestFixture, + ::testing::Values( + // single shape + std::make_tuple("[3,4]", Shapes{Shape{3, 4}}), + std::make_tuple("[3]", Shapes{Shape{3}}), + std::make_tuple("[-1]", Shapes{Shape{Dim::dynamic()}}), + std::make_tuple("[ 5, 6]", Shapes{Shape{5, 6}}), + std::make_tuple("[3 , 4]", Shapes{Shape{3, 4}}), + std::make_tuple("[-1 , 4]", Shapes{Shape{Dim::dynamic(), Dim{4}}}), + // many shapes + std::make_tuple("[3,4],[5,6]", Shapes{Shape{3, 4}, Shape{5, 6}}), + std::make_tuple("[1],[2]", Shapes{Shape{1}, Shape{2}}), + std::make_tuple(" [3, 4] , [5,6]", Shapes{Shape{3, 4}, Shape{5, 6}}), + std::make_tuple(" [ 1 ] ,[ 2]", Shapes{Shape{1}, Shape{2}}), + std::make_tuple(" [ 1 ] ,[ 2] ", Shapes{Shape{1}, Shape{2}}), + std::make_tuple(" [1,2],[3,4,5],[6,7,8,9]", + Shapes{Shape{1, 2}, Shape{3, 4, 5}, Shape{6, 7, 8, 9}}), + // scalars + std::make_tuple("[]", Shapes{Shape::scalar()}), + std::make_tuple("[],[]", Shapes{Shape::scalar(), Shape::scalar()}), + std::make_tuple("[],[2]", Shapes{Shape::scalar(), Shape{2}}), + std::make_tuple("[ ]", Shapes{Shape::scalar()}))); class InvalidArgParseShapeTestFixture : public ::testing::TestWithParam { From 0d3b255495c77a8a7b2deef46b48ba83b29667aa Mon Sep 17 00:00:00 2001 From: Mateusz Bencer Date: Mon, 14 Apr 2025 08:22:51 +0200 Subject: [PATCH 8/8] review remarks --- compiler/circle-resizer/include/Dim.h | 2 ++ compiler/circle-resizer/include/Shape.h | 1 + compiler/circle-resizer/include/ShapeParser.h | 2 ++ compiler/circle-resizer/src/ShapeParser.cpp | 6 ++++++ compiler/circle-resizer/tests/ShapeParser.test.cpp | 3 ++- 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/compiler/circle-resizer/include/Dim.h b/compiler/circle-resizer/include/Dim.h index 615baaf0cee..22d651c16e6 100644 --- a/compiler/circle-resizer/include/Dim.h +++ b/compiler/circle-resizer/include/Dim.h @@ -21,6 +21,7 @@ namespace circle_resizer { + /** * The representation of a single dimension. Note that a dimension can be dynamic. */ @@ -60,6 +61,7 @@ class Dim // 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 index f935c6187c4..5b5d6debdba 100644 --- a/compiler/circle-resizer/include/Shape.h +++ b/compiler/circle-resizer/include/Shape.h @@ -24,6 +24,7 @@ namespace circle_resizer { + /** * The representation of a single shape. */ diff --git a/compiler/circle-resizer/include/ShapeParser.h b/compiler/circle-resizer/include/ShapeParser.h index 7da8baef7e5..e1af93b9526 100644 --- a/compiler/circle-resizer/include/ShapeParser.h +++ b/compiler/circle-resizer/include/ShapeParser.h @@ -24,6 +24,7 @@ namespace circle_resizer { + /** * @brief Parse shapes from string representation to Shapes object. * @@ -35,6 +36,7 @@ namespace circle_resizer * std::invalid_argument if the parsing failed. */ std::vector parse_shapes(const std::string &shapes); + } // namespace circle_resizer #endif // __CIRCLE_RESIZER_SHAPE_PARSER_H__ diff --git a/compiler/circle-resizer/src/ShapeParser.cpp b/compiler/circle-resizer/src/ShapeParser.cpp index 94edb2e4623..baa189f4160 100644 --- a/compiler/circle-resizer/src/ShapeParser.cpp +++ b/compiler/circle-resizer/src/ShapeParser.cpp @@ -25,6 +25,7 @@ using namespace circle_resizer; namespace { + bool is_blank(const std::string &s) { return !s.empty() && std::find_if(s.begin(), s.end(), @@ -58,6 +59,7 @@ Shape parse_single_shape(const std::string &shape) } return Shape{result_dims}; } + } // namespace std::vector circle_resizer::parse_shapes(const std::string &shapes) @@ -69,6 +71,10 @@ std::vector circle_resizer::parse_shapes(const std::string &shapes) while ((begin_pos = shapes_tmp.find_first_of("[")) != std::string::npos && (end_pos = shapes_tmp.find_first_of("]")) != std::string::npos) { + if (begin_pos > end_pos) + { + throw std::invalid_argument("Invalid shape format: " + shapes); + } const size_t token_size = end_pos - begin_pos - 1; token = shapes_tmp.substr(begin_pos + 1, token_size); result_shapes.push_back(parse_single_shape(token)); diff --git a/compiler/circle-resizer/tests/ShapeParser.test.cpp b/compiler/circle-resizer/tests/ShapeParser.test.cpp index 1473ff954bd..521d4deb869 100644 --- a/compiler/circle-resizer/tests/ShapeParser.test.cpp +++ b/compiler/circle-resizer/tests/ShapeParser.test.cpp @@ -75,4 +75,5 @@ INSTANTIATE_TEST_SUITE_P(InvalidArgParseShape, InvalidArgParseShapeTestFixture, std::string{"[-2]"}, std::string{"[-2,1,3]"}, std::string{"-1"}, std::string{"7,7"}, std::string{"8"}, std::string{"[8],9"}, std::string{"1,2"}, - std::string{"[1],[2],"}, std::string{"[[]],"})); + std::string{"[1],[2],"}, std::string{"[[]],"}, + std::string{"][1]"}, std::string{"]["}));