diff --git a/aidge_core/unit_tests/test_operator_binding.py b/aidge_core/unit_tests/test_operator_binding.py index 5b25eb7975d439816dbf91cc95b462f217fd0227..8d6f2686d9010ac4ebed80cd04f74effe763e977 100644 --- a/aidge_core/unit_tests/test_operator_binding.py +++ b/aidge_core/unit_tests/test_operator_binding.py @@ -73,15 +73,26 @@ class test_operator_binding(unittest.TestCase): self.assertEqual(attrs.get_attr("b"), "test") self.assertEqual(attrs.has_attr("c"), True) self.assertEqual(attrs.get_attr("c"), [True, False, True]) - self.assertEqual(attrs.dict().keys(), {"a", "b", "c"}) + self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "mem", "impl"}) self.assertEqual(attrs.has_attr("d"), False) + self.assertEqual(attrs.has_attr("mem.a"), True) + self.assertEqual(attrs.get_attr("mem.a"), 1) + self.assertEqual(attrs.has_attr("mem.data.b"), True) + self.assertEqual(attrs.get_attr("mem.data.b"), 1.0) + self.assertEqual(attrs.get_attr("mem").get_attr("data").get_attr("b"), 1.0) + self.assertEqual(attrs.has_attr("impl.c"), True) + self.assertEqual(attrs.get_attr("impl.c"), "test") # Add Python attributes attrs.add_attr("d", 18.56) self.assertEqual(attrs.get_attr("d"), 18.56) self.assertEqual(attrs.has_attr("d"), True) - self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "d"}) + self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "d", "mem", "impl"}) self.assertEqual(attrs.has_attr("e"), False) + attrs.add_attr("mem.data.c", 19.36) + self.assertEqual(attrs.get_attr("mem.data.c"), 19.36) + self.assertEqual(attrs.has_attr("mem.data.c"), True) + self.assertEqual(attrs.dict().keys(), {"a", "b", "c", "d", "mem", "impl"}) # Check that added Python attribute is accessible in C++ # Return the value of an attribute named "d" of type float64 (double in C++) @@ -89,6 +100,23 @@ class test_operator_binding(unittest.TestCase): attrs.d = 23.89 self.assertEqual(aidge_core.test_DynamicAttributes_binding_check(attrs), 23.89) + op = aidge_core.GenericOperatorOp("any_type", 1,0,1) + with self.assertRaises(RuntimeError): + op.attr.something + + op.attr.something = aidge_core.DynamicAttributes() + try: + self.assertEqual(str(op.attr), "AttrDict({'something': AttrDict({})})") + except Exception: + self.fail("op.attr.something raised Exception unexpectedly!") + + op.attr.something.arg1 = 4 + self.assertEqual(op.attr.something.arg1, 4) + + # auto create the namespace another_thing (not enabled) + #op.attr.another_thing.arg = 44 + #self.assertEqual(op.attr.another_thing.arg, 44) + def test_forward_dims(self): in_dims=[25, 25] input = aidge_core.Producer(in_dims, name="In") diff --git a/include/aidge/operator/AvgPooling.hpp b/include/aidge/operator/AvgPooling.hpp index 06ee4327e2f2d4df32c2decd73841bdf5f79a739..920829473d856b2a4c14fc0859abcd4c3b70277a 100644 --- a/include/aidge/operator/AvgPooling.hpp +++ b/include/aidge/operator/AvgPooling.hpp @@ -119,8 +119,8 @@ extern template class Aidge::AvgPooling_Op<4>; namespace { template <> const char *const EnumStrings<Aidge::AvgPoolingAttr>::data[] = { - "StrideDims", - "KernelDims" + "stride_dims", + "kernel_dims" }; } diff --git a/include/aidge/operator/BatchNorm.hpp b/include/aidge/operator/BatchNorm.hpp index b5b64eb428d709e804dd9f6711530b348e0be747..08d1f6a88d394e34dd6e351f500429113a52c9fa 100644 --- a/include/aidge/operator/BatchNorm.hpp +++ b/include/aidge/operator/BatchNorm.hpp @@ -111,7 +111,7 @@ extern template std::shared_ptr<Aidge::Node> Aidge::BatchNorm<4>(const DimSize_t namespace { template <> -const char *const EnumStrings<Aidge::BatchNormAttr>::data[] = { "Epsilon", "Momentum" }; +const char *const EnumStrings<Aidge::BatchNormAttr>::data[] = { "epsilon", "momentum" }; } #endif //AIDGE_CORE_OPERATOR_BATCHNORM_H_ diff --git a/include/aidge/operator/Cast.hpp b/include/aidge/operator/Cast.hpp index 6911053932afff6675be4eb2c713d8d3cd34b462..291669b7c57c14a77ffa6b40fa2aefab8d281fc7 100644 --- a/include/aidge/operator/Cast.hpp +++ b/include/aidge/operator/Cast.hpp @@ -93,7 +93,7 @@ inline std::shared_ptr<Node> Cast(const DataType targetType, const std::string& namespace { template <> -const char* const EnumStrings<Aidge::CastAttr>::data[] = { "TargetType" }; +const char* const EnumStrings<Aidge::CastAttr>::data[] = { "target_type" }; } #endif /* AIDGE_CORE_OPERATOR_CAST_H_ */ diff --git a/include/aidge/operator/Concat.hpp b/include/aidge/operator/Concat.hpp index 8341a93fe66d260ae3687170629b8759d0305a9c..ab14bf527dd9949f3bb2b6157619e58c7c7580ee 100644 --- a/include/aidge/operator/Concat.hpp +++ b/include/aidge/operator/Concat.hpp @@ -108,7 +108,7 @@ inline std::shared_ptr<Node> Concat(const IOIndex_t nbIn, const std::int32_t axi namespace { template <> const char* const EnumStrings<Aidge::ConcatAttr>::data[] = { - "Axis" + "axis" }; } diff --git a/include/aidge/operator/Conv.hpp b/include/aidge/operator/Conv.hpp index 87ff5854b310ca472994bd6b68fd6ae58d31e806..e89c94f968ab89f43e6ef2d95a40a6f557cc41c7 100644 --- a/include/aidge/operator/Conv.hpp +++ b/include/aidge/operator/Conv.hpp @@ -178,9 +178,9 @@ extern template class Aidge::Conv_Op<2>; namespace { template <> const char *const EnumStrings<Aidge::ConvAttr>::data[] = { - "StrideDims", - "DilationDims", - "KernelDims" + "stride_dims", + "dilation_dims", + "kernel_dims" }; } diff --git a/include/aidge/operator/ConvDepthWise.hpp b/include/aidge/operator/ConvDepthWise.hpp index c8a83ff7de62a61e8125eac29d61c3938115cd09..1acf240bfcdd256953cd96b92e3622a265aafa0b 100644 --- a/include/aidge/operator/ConvDepthWise.hpp +++ b/include/aidge/operator/ConvDepthWise.hpp @@ -140,8 +140,8 @@ extern template class Aidge::ConvDepthWise_Op<2>; namespace { template <> -const char *const EnumStrings<Aidge::ConvDepthWiseAttr>::data[] = {"StrideDims", "DilationDims", - "KernelDims"}; +const char *const EnumStrings<Aidge::ConvDepthWiseAttr>::data[] = {"stride_dims", "dilation_dims", + "kernel_dims"}; } #endif /* AIDGE_CORE_OPERATOR_CONVDEPTHWISE_H_ */ diff --git a/include/aidge/operator/Fold.hpp b/include/aidge/operator/Fold.hpp index 28127f9efe437531a64d228f7ed9c168edc39eb6..caf904e870425c000687ccd95397c92744020eec 100644 --- a/include/aidge/operator/Fold.hpp +++ b/include/aidge/operator/Fold.hpp @@ -133,10 +133,10 @@ extern template class Aidge::Fold_Op<2>; namespace { template <> const char *const EnumStrings<Aidge::FoldAttr>::data[] = { - "OutputDims", - "StrideDims", - "DilationDims", - "KernelDims" + "output_dims", + "stride_dims", + "dilation_dims", + "kernel_dims" }; } diff --git a/include/aidge/operator/Gather.hpp b/include/aidge/operator/Gather.hpp index 3e9b780732fa9144f2e58bef854d1b42d063d0bf..5f3917e486e2e2188bfd23bd58a13b51d5fc7a59 100644 --- a/include/aidge/operator/Gather.hpp +++ b/include/aidge/operator/Gather.hpp @@ -114,7 +114,7 @@ inline std::shared_ptr<Node> Gather(std::int8_t axis = 0, const std::vector<int6 namespace { template <> -const char *const EnumStrings<Aidge::GatherAttr>::data[] = {"Axis", "Indices", "GatheredShape"}; +const char *const EnumStrings<Aidge::GatherAttr>::data[] = {"axis", "indices", "gathered_shape"}; } #endif /* AIDGE_CORE_OPERATOR_GATHER_H_ */ diff --git a/include/aidge/operator/LeakyReLU.hpp b/include/aidge/operator/LeakyReLU.hpp index 294e7ebb009ff184c9150d2aa18067a15deeba22..3057b99f70fa3693f7e434be29dcd40fb98d4bea 100644 --- a/include/aidge/operator/LeakyReLU.hpp +++ b/include/aidge/operator/LeakyReLU.hpp @@ -99,7 +99,7 @@ inline std::shared_ptr<Node> LeakyReLU(float negativeSlope = 0.0f, const std::st namespace { template <> const char* const EnumStrings<Aidge::LeakyReLUAttr>::data[] - = {"NegativeSlope"}; + = {"negative_slope"}; } #endif /* AIDGE_CORE_OPERATOR_RELU_H_ */ diff --git a/include/aidge/operator/MaxPooling.hpp b/include/aidge/operator/MaxPooling.hpp index 082aa26bbdf1d55dcae29d1ffb2b9810db8b17d0..7e2c68681e645133812103a94e4c39ab9d1dc970 100644 --- a/include/aidge/operator/MaxPooling.hpp +++ b/include/aidge/operator/MaxPooling.hpp @@ -156,7 +156,7 @@ inline std::shared_ptr<Node> MaxPooling( namespace { template <> -const char *const EnumStrings<Aidge::MaxPoolingAttr>::data[] = {"StrideDims", "KernelDims", "CeilMode"}; +const char *const EnumStrings<Aidge::MaxPoolingAttr>::data[] = {"stride_dims", "kernel_dims", "ceil_mode"}; } #endif /* AIDGE_CORE_OPERATOR_MAXPOOLING_H_ */ diff --git a/include/aidge/operator/Memorize.hpp b/include/aidge/operator/Memorize.hpp index d6af56f2faad18b9e39c793ea68e39eac4dd2f01..bb652e833ad06df37f55d3582afd0e66cc3e97c8 100644 --- a/include/aidge/operator/Memorize.hpp +++ b/include/aidge/operator/Memorize.hpp @@ -113,9 +113,9 @@ inline std::shared_ptr<Node> Memorize(const std::uint32_t endStep, const std::st namespace { template <> const char *const EnumStrings<Aidge::MemorizeAttr>::data[] = { - "ScheduleStep", - "ForwardStep", - "EndStep" + "schedule_step", + "forward_step", + "end_step" }; } diff --git a/include/aidge/operator/Pad.hpp b/include/aidge/operator/Pad.hpp index 5fd0f93986206e6cd958a85055159783eeb8bc8f..215fafb7fee10587dec38e77685d705f7c1bb980 100644 --- a/include/aidge/operator/Pad.hpp +++ b/include/aidge/operator/Pad.hpp @@ -139,7 +139,7 @@ extern template class Aidge::Pad_Op<2>; namespace { template <> -const char *const EnumStrings<Aidge::PadAttr>::data[] = {"BeginEndBorders", "BorderType", "BorderValue"}; +const char *const EnumStrings<Aidge::PadAttr>::data[] = {"begin_end_borders", "border_type", "border_value"}; template <> const char *const EnumStrings<Aidge::PadBorderType>::data[] = {"Constant", "Edge", "Reflect", "Wrap"}; diff --git a/include/aidge/operator/Pop.hpp b/include/aidge/operator/Pop.hpp index 575d56b455940ea98571110dbaa9a83de09fef37..fb3b32eeacf2e199df88b6bd0256cf6cbdaa1065 100644 --- a/include/aidge/operator/Pop.hpp +++ b/include/aidge/operator/Pop.hpp @@ -100,7 +100,7 @@ inline std::shared_ptr<Node> Pop(const std::string& name = "") { namespace { template <> const char *const EnumStrings<Aidge::PopAttr>::data[] = { - "ForwardStep" + "forward_step" }; } diff --git a/include/aidge/operator/Producer.hpp b/include/aidge/operator/Producer.hpp index 6b37fbd13b6e29f4b11a9e1628da9dbd7b35bb35..1647c563d38ab4931cc3a0c2a4281555215f990e 100644 --- a/include/aidge/operator/Producer.hpp +++ b/include/aidge/operator/Producer.hpp @@ -160,7 +160,7 @@ std::shared_ptr<Node> addProducer(std::shared_ptr<Node>& otherNode, const IOInde namespace { template <> const char *const EnumStrings<Aidge::ProdAttr>::data[] = { - "Constant" + "constant" }; } #endif /* AIDGE_CORE_OPERATOR_PRODUCER_H_ */ diff --git a/include/aidge/operator/ReduceMean.hpp b/include/aidge/operator/ReduceMean.hpp index 3fcf19ffd13645fb28b6efcfefaf8e347b148c89..000607c60e4e3c85671e70a941bd11f3427333dd 100644 --- a/include/aidge/operator/ReduceMean.hpp +++ b/include/aidge/operator/ReduceMean.hpp @@ -127,7 +127,7 @@ inline std::shared_ptr<Node> ReduceMean(const std::vector<std::int32_t> &axes, namespace { template <> -const char *const EnumStrings<Aidge::ReduceMeanAttr>::data[] = {"Axes", "KeepDims"}; +const char *const EnumStrings<Aidge::ReduceMeanAttr>::data[] = {"axes", "keep_dims"}; } #endif /* AIDGE_CORE_OPERATOR_REDUCEMEAN_H_ */ diff --git a/include/aidge/operator/Reshape.hpp b/include/aidge/operator/Reshape.hpp index 4ea0cca30089555ff7979f141f94e5c84f04ffa1..29a08c76c248018fff87a5f765a0b62cbd23b6b7 100644 --- a/include/aidge/operator/Reshape.hpp +++ b/include/aidge/operator/Reshape.hpp @@ -107,7 +107,7 @@ inline std::shared_ptr<Node> Reshape(const std::vector<std::int64_t>& shape = {} namespace { template <> -const char *const EnumStrings<Aidge::ReshapeAttr>::data[] = { "Shape", "AllowZero" }; +const char *const EnumStrings<Aidge::ReshapeAttr>::data[] = { "shape", "allow_zero" }; } #endif /* AIDGE_CORE_OPERATOR_RESHAPE_H_ */ diff --git a/include/aidge/operator/Scaling.hpp b/include/aidge/operator/Scaling.hpp index 7d8e11b31546cd87a8d6b2d36e2929c9ef6df7a2..0683a26f6e9d8ef462c2af4693f372b43c33a144 100644 --- a/include/aidge/operator/Scaling.hpp +++ b/include/aidge/operator/Scaling.hpp @@ -105,7 +105,7 @@ inline std::shared_ptr<Node> Scaling(float scalingFactor = 1.0f, namespace { template <> const char* const EnumStrings<Aidge::ScalingAttr>::data[] - = {"ScalingFactor", "QuantizedNbBits", "IsOutputUnsigned"}; + = {"scaling_factor", "quantized_nb_bits", "is_output_unsigned"}; } #endif /* AIDGE_CORE_OPERATOR_SCALING_H_ */ diff --git a/include/aidge/operator/Shape.hpp b/include/aidge/operator/Shape.hpp index 6d2d1b5e7c212fafa5ad6457d9e0a260e96b1c90..94f237726e79d8fe7824ff2c9b2f7640bbfc716f 100644 --- a/include/aidge/operator/Shape.hpp +++ b/include/aidge/operator/Shape.hpp @@ -105,7 +105,7 @@ inline std::shared_ptr<Node> Shape(const std::int64_t start = 0, const std::int6 namespace { template <> -const char *const EnumStrings<Aidge::ShapeAttr>::data[] = {"Start", "End"}; +const char *const EnumStrings<Aidge::ShapeAttr>::data[] = {"start", "end"}; } #endif /* AIDGE_CORE_OPERATOR_SHAPE_H_ */ diff --git a/include/aidge/operator/Slice.hpp b/include/aidge/operator/Slice.hpp index 7d425a0f3589e74b54ee0834fdc4291ea7f49bad..04a67fe98f7682737bff6df18f28d568ee33e093 100644 --- a/include/aidge/operator/Slice.hpp +++ b/include/aidge/operator/Slice.hpp @@ -115,7 +115,7 @@ inline std::shared_ptr<Node> Slice(const std::vector<std::int64_t>& starts = {}, namespace { template <> -const char *const EnumStrings<Aidge::SliceAttr>::data[] = { "Starts", "Ends", "Axes", "Steps" }; +const char *const EnumStrings<Aidge::SliceAttr>::data[] = { "starts", "ends", "axes", "steps" }; } #endif /* AIDGE_CORE_OPERATOR_RELU_H_ */ diff --git a/include/aidge/operator/Softmax.hpp b/include/aidge/operator/Softmax.hpp index 70f3a561ae5c9ba4720de8419bcd5aaf32a51e47..0b7a8e57193439872c6fcc2699b9f5e55c643961 100644 --- a/include/aidge/operator/Softmax.hpp +++ b/include/aidge/operator/Softmax.hpp @@ -92,7 +92,7 @@ inline std::shared_ptr<Node> Softmax(std::int32_t axis, const std::string& name namespace { template <> -const char *const EnumStrings<Aidge::SoftmaxAttr>::data[] = {"Axis"}; +const char *const EnumStrings<Aidge::SoftmaxAttr>::data[] = {"axis"}; } #endif /* AIDGE_CORE_OPERATOR_SOFTMAX_H_ */ diff --git a/include/aidge/operator/Split.hpp b/include/aidge/operator/Split.hpp index 42baf66e6722c6f9a0d3f40f12d4f4685fcc6980..7bdec1579c8a8f46640de5caf42c01568d208059 100644 --- a/include/aidge/operator/Split.hpp +++ b/include/aidge/operator/Split.hpp @@ -34,20 +34,24 @@ enum class SplitAttr { Axis, Split }; class Split_Op : public OperatorTensor, - public Registrable<Split_Op, std::string, std::shared_ptr<OperatorImpl>(const Split_Op &)>, - public StaticAttributes<SplitAttr, std::int8_t, std::vector<DimSize_t>> { + public Registrable<Split_Op, std::string, std::shared_ptr<OperatorImpl>(const Split_Op &)> { public: static const std::string Type; +private: + using Attributes_ = StaticAttributes<SplitAttr, std::int8_t, std::vector<DimSize_t>>; + template <SplitAttr e> using attr = typename Attributes_::template attr<e>; + const std::shared_ptr<Attributes_> mAttributes; + +public: Split_Op() = delete; - using Attributes_ = StaticAttributes<SplitAttr, std::int8_t, std::vector<DimSize_t>>; - template <SplitAttr e> using attr = typename Attributes_::template attr<e>; Split_Op( std::int8_t axis, DimSize_t nbOutputs, const std::vector<DimSize_t>& split) : OperatorTensor(Type, {InputCategory::Data, InputCategory::OptionalData}, nbOutputs), - Attributes_(attr<SplitAttr::Axis>(axis), - attr<SplitAttr::Split>(split)) + mAttributes(std::make_shared<Attributes_>( + attr<SplitAttr::Axis>(axis), + attr<SplitAttr::Split>(split))) { mImpl = std::make_shared<Split_OpImpl>(*this); } @@ -60,7 +64,7 @@ public: */ Split_Op(const Split_Op &op) : OperatorTensor(op), - Attributes_(op) + mAttributes(op.mAttributes) { if (!op.backend().empty()) { SET_IMPL_MACRO(Split_Op, *this, op.backend()); @@ -81,6 +85,10 @@ public: void setBackend(const std::string &name, DeviceIdx_t device = 0) override; + inline std::shared_ptr<Attributes> attributes() const override { return mAttributes; } + inline std::int8_t& axis() const { return mAttributes->template getAttr<SplitAttr::Axis>(); } + inline std::vector<DimSize_t>& split() const { return mAttributes->template getAttr<SplitAttr::Split>(); } + static const std::vector<std::string> getInputsName(){ return {"data_input", "split"}; } @@ -105,7 +113,7 @@ inline std::shared_ptr<Node> Split(DimSize_t nbOutput, namespace { template <> -const char *const EnumStrings<Aidge::SplitAttr>::data[] = { "Axis", "Split" }; +const char *const EnumStrings<Aidge::SplitAttr>::data[] = { "axis", "split" }; } #endif /* AIDGE_CORE_OPERATOR_SPLIT_H_ */ diff --git a/include/aidge/operator/Transpose.hpp b/include/aidge/operator/Transpose.hpp index 72096448ebf0e00d73e33bdab094ca7f0b7d0633..efd9e1792d530f45754809913a7c648d82c7985e 100644 --- a/include/aidge/operator/Transpose.hpp +++ b/include/aidge/operator/Transpose.hpp @@ -105,7 +105,7 @@ inline std::shared_ptr<Node> Transpose(const std::vector<DimSize_t> &outputDimsO namespace { template <> -const char *const EnumStrings<Aidge::TransposeAttr>::data[] = {"OutputDimsOrder"}; +const char *const EnumStrings<Aidge::TransposeAttr>::data[] = {"output_dims_order"}; } #endif /* AIDGE_CORE_OPERATOR_TRANSPOSE_H_ */ diff --git a/include/aidge/operator/Unfold.hpp b/include/aidge/operator/Unfold.hpp index 169fbb05ebeff0e5d38eb9606133d6279cc31cd8..58cbcd2d756ad44ef2ec6a38d46909a114b187c2 100644 --- a/include/aidge/operator/Unfold.hpp +++ b/include/aidge/operator/Unfold.hpp @@ -137,9 +137,9 @@ extern template class Aidge::Unfold_Op<2>; namespace { template <> const char *const EnumStrings<Aidge::UnfoldAttr>::data[] = { - "StrideDims", - "DilationDims", - "KernelDims" + "stride_dims", + "dilation_dims", + "kernel_dims" }; } diff --git a/include/aidge/utils/Attributes.hpp b/include/aidge/utils/Attributes.hpp index c1f6a8a7f704b4bd813983cb178d9e5acba5a5e1..7dce3d327d42de15dc2589788b4643742ed1a463 100644 --- a/include/aidge/utils/Attributes.hpp +++ b/include/aidge/utils/Attributes.hpp @@ -42,32 +42,6 @@ constexpr std::size_t size(T (&)[N]) { return N; } * Attributes in the binding code. */ class Attributes { -protected: - /** - * @brief Convert snake_case to PascalCase. - * @param snakeCase string to convert. - */ - static std::string snakeToPascal(const std::string& snakeCase); - - - /** - * @brief Convert PascalCase to snake_case. - * @param pascalCase string to convert. - */ - static std::string pascalToSnake(const std::string& pascalCase); - - /** - * @brief Check whether a given string is in PascalCase. - * @param str String to check. - */ - static bool isPascalCase(const std::string& str); - - /** - * @brief Check whether a given string is in snake_case. - * @param str String to check. - */ - static bool isSnakeCase(const std::string& str); - public: /** * @brief Check if the attribute exists. diff --git a/include/aidge/utils/DynamicAttributes.hpp b/include/aidge/utils/DynamicAttributes.hpp index c5054eb2fd2e8bfa5e7fca898f343ce630643dbd..cf7f048dbe5999f433277c46e4e3cb9798c43674 100644 --- a/include/aidge/utils/DynamicAttributes.hpp +++ b/include/aidge/utils/DynamicAttributes.hpp @@ -46,40 +46,35 @@ public: * exist * \note at() throws if the Attribute does not exist, using find to test for Attribute existance */ - template<class T> T& getAttr(const std::string& name) + template<class T> const T& getAttr(const std::string& name) const { - AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes."); + const auto dot = name.find('.'); + if (dot == name.npos) { #ifdef PYBIND - // If attribute does not exist in C++, it might have been created or modified in Python - auto it = mAttrs.find(name); - if (it == mAttrs.end()) { - auto itPy = mAttrsPy.find(pascalToSnake(name)); - if (itPy != mAttrsPy.end()) { - // Insert the attribute back in C++ - mAttrs.emplace(std::make_pair(name, future_std::any(itPy->second.cast<T>()))); + // If attribute does not exist in C++, it might have been created or modified in Python + auto it = mAttrs.find(name); + if (it == mAttrs.end()) { + auto itPy = mAttrsPy.find(name); + if (itPy != mAttrsPy.end()) { + // Insert the attribute back in C++ + mAttrs.emplace(std::make_pair(name, future_std::any(itPy->second.cast<T>()))); + } } - } #endif - return future_std::any_cast<T&>(mAttrs.at(name)); - } - - template<class T> const T& getAttr(const std::string& name) const - { - AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes."); -#ifdef PYBIND - // If attribute does not exist in C++, it might have been created or modified in Python - auto it = mAttrs.find(name); - if (it == mAttrs.end()) { - auto itPy = mAttrsPy.find(pascalToSnake(name)); - if (itPy != mAttrsPy.end()) { - // Insert the attribute back in C++ - mAttrs.emplace(std::make_pair(name, future_std::any(itPy->second.cast<T>()))); - } + return future_std::any_cast<const T&>(mAttrs.at(name)); } -#endif + else { + const auto ns = name.substr(0, dot); + const auto nsName = name.substr(dot + 1); + return future_std::any_cast<const DynamicAttributes&>(mAttrs.at(ns)).getAttr<T>(nsName); + } + } - return future_std::any_cast<const T&>(mAttrs.at(name)); + template<class T> T& getAttr(const std::string& name) { + // Scott Meyers' solution to avoid code duplication + return const_cast<T&>( + static_cast<const DynamicAttributes&>(*this).getAttr<T>(name)); } ///\brief Add a new Attribute, identified by its name. If it already exists, asserts. @@ -88,17 +83,26 @@ public: ///\param value Attribute value template<class T> void addAttr(const std::string& name, const T& value) { - AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes."); - const auto& res = mAttrs.emplace(std::make_pair(name, future_std::any(value))); - AIDGE_ASSERT(res.second, "attribute already exists"); + const auto dot = name.find('.'); + if (dot == name.npos) { + const auto& res = mAttrs.emplace(std::make_pair(name, future_std::any(value))); + AIDGE_ASSERT(res.second, "addAttr(): attribute \"{}\" already exists. Use setAttr() if this is expected.", name); #ifdef PYBIND - // We cannot handle Python object if the Python interpreter is not running - if (Py_IsInitialized()) { - // Keep a copy of the attribute in py::object that is updated everytime - mAttrsPy.emplace(std::make_pair(pascalToSnake(name), py::cast(value))); - } + // We cannot handle Python object if the Python interpreter is not running + if (Py_IsInitialized()) { + // Keep a copy of the attribute in py::object that is updated everytime + const auto& resPy = mAttrsPy.emplace(std::make_pair(name, py::cast(value))); + AIDGE_ASSERT(resPy.second, "addAttr(): attribute \"{}\" already exists (added in Python). Use setAttr() if this is expected.", name); + } #endif + } + else { + const auto ns = name.substr(0, dot); + const auto nsName = name.substr(dot + 1); + const auto& res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes()))); + future_std::any_cast<DynamicAttributes&>(res.first->second).addAttr(nsName, value); + } } ///\brief Set an Attribute value, identified by its name. If it already exists, its value (and type, if different) is changed. @@ -107,56 +111,94 @@ public: ///\param value Attribute value template<class T> void setAttr(const std::string& name, const T& value) { - auto res = mAttrs.emplace(std::make_pair(name, future_std::any(value))); - if (!res.second) - res.first->second = future_std::any(value); + const auto dot = name.find('.'); + if (dot == name.npos) { + auto res = mAttrs.emplace(std::make_pair(name, future_std::any(value))); + if (!res.second) + res.first->second = future_std::any(value); #ifdef PYBIND - // We cannot handle Python object if the Python interpreter is not running - if (Py_IsInitialized()) { - // Keep a copy of the attribute in py::object that is updated everytime - auto resPy = mAttrsPy.emplace(std::make_pair(name, py::cast(value))); - if (!resPy.second) - resPy.first->second = std::move(py::cast(value)); - } + // We cannot handle Python object if the Python interpreter is not running + if (Py_IsInitialized()) { + // Keep a copy of the attribute in py::object that is updated everytime + auto resPy = mAttrsPy.emplace(std::make_pair(name, py::cast(value))); + if (!resPy.second) + resPy.first->second = std::move(py::cast(value)); + } #endif + } + else { + const auto ns = name.substr(0, dot); + const auto nsName = name.substr(dot + 1); + auto res = mAttrs.emplace(std::make_pair(ns, future_std::any(DynamicAttributes()))); + future_std::any_cast<DynamicAttributes&>(res.first->second).setAttr<T>(nsName, value); + } } void delAttr(const std::string& name) { - mAttrs.erase(name); + const auto dot = name.find('.'); + if (dot == name.npos) { + mAttrs.erase(name); #ifdef PYBIND - mAttrsPy.erase(name); + mAttrsPy.erase(name); #endif + } + else { + const auto ns = name.substr(0, dot); + const auto nsName = name.substr(dot + 1); + future_std::any_cast<DynamicAttributes&>(mAttrs.at(ns)).delAttr(nsName); + } } #ifdef PYBIND void addAttrPy(const std::string& name, py::object&& value) { - AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python."); - auto it = mAttrs.find(snakeToPascal(name)); - AIDGE_ASSERT(it == mAttrs.end(), "attribute already exists"); + const auto dot = name.find('.'); + if (dot == name.npos) { + auto it = mAttrs.find(name); + AIDGE_ASSERT(it == mAttrs.end(), "add_attr(): attribute \"{}\" already exists (added in C++). Use set_attr() if this is expected.", name); + + const auto& res = mAttrsPy.emplace(std::make_pair(name, value)); + AIDGE_ASSERT(res.second, "add_attr(): attribute \"{}\" already exists. Use set_attr() if this is expected.", name); + } + else { + const auto ns = name.substr(0, dot); + const auto nsName = name.substr(dot + 1); + const auto& res = mAttrs.emplace(std::make_pair(ns, DynamicAttributes())); - const auto& res = mAttrsPy.emplace(std::make_pair(name, value)); - AIDGE_ASSERT(res.second, "attribute already exists"); + future_std::any_cast<DynamicAttributes&>(res.first->second).addAttrPy(nsName, std::move(value)); + } } void setAttrPy(const std::string& name, py::object&& value) override final { - AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python."); - auto resPy = mAttrsPy.emplace(std::make_pair(name, value)); - if (!resPy.second) - resPy.first->second = std::move(value); - - // Force getAttr() to take attribute value from mAttrsPy and update mAttrs - const std::string pascalName = snakeToPascal(name); - mAttrs.erase(pascalName); + const auto dot = name.find('.'); + if (dot == name.npos) { + auto resPy = mAttrsPy.emplace(std::make_pair(name, value)); + if (!resPy.second) + resPy.first->second = std::move(value); + + // Force getAttr() to take attribute value from mAttrsPy and update mAttrs + mAttrs.erase(name); + } + else { + const auto ns = name.substr(0, dot); + const auto nsName = name.substr(dot + 1); + const auto& res = mAttrs.emplace(std::make_pair(ns, DynamicAttributes())); + + future_std::any_cast<DynamicAttributes&>(res.first->second).setAttrPy(nsName, std::move(value)); + } } py::dict dict() const override { py::dict attributes; + for (const auto& elt : mAttrs) { + if (elt.second.type() == typeid(DynamicAttributes)) { + attributes[elt.first.c_str()] = future_std::any_cast<const DynamicAttributes&>(elt.second).dict(); + } + } for (const auto& elt : mAttrsPy) { - const std::string snakeName = pascalToSnake(elt.first); - attributes[snakeName.c_str()] = elt.second; + attributes[elt.first.c_str()] = elt.second; } return attributes; } @@ -177,15 +219,46 @@ public: /// Generic Attributes API ////////////////////////////////////// bool hasAttr(const std::string& name) const override final { - AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes."); - return (mAttrs.find(name) != mAttrs.cend()); + const auto dot = name.find('.'); + if (dot == name.npos) { +#ifdef PYBIND + return (mAttrs.find(name) != mAttrs.cend() || mAttrsPy.find(name) != mAttrsPy.cend()); + +#else + return (mAttrs.find(name) != mAttrs.cend()); +#endif + } + else { + const auto ns = name.substr(0, dot); + const auto it = mAttrs.find(ns); + if (it != mAttrs.cend()) { + const auto nsName = name.substr(dot + 1); + return future_std::any_cast<const DynamicAttributes&>(it->second).hasAttr(nsName); + } + else { + return false; + } + } } #ifdef PYBIND bool hasAttrPy(const std::string& name) const override final { - AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python."); - // Attributes might have been created in Python, the second condition is necessary. - return (mAttrs.find(snakeToPascal(name)) != mAttrs.cend() || mAttrsPy.find(name) != mAttrsPy.cend()); + const auto dot = name.find('.'); + if (dot == name.npos) { + // Attributes might have been created in Python, the second condition is necessary. + return (mAttrs.find(name) != mAttrs.cend() || mAttrsPy.find(name) != mAttrsPy.cend()); + } + else { + const auto ns = name.substr(0, dot); + const auto it = mAttrs.find(ns); + if (it != mAttrs.cend()) { + const auto nsName = name.substr(dot + 1); + return future_std::any_cast<const DynamicAttributes&>(it->second).hasAttrPy(nsName); + } + else { + return false; + } + } } #endif @@ -193,18 +266,26 @@ public: // In order to remain consistent between C++ and Python, with or without PyBind, the name of the type is: // - C-style for C++ created attributes // - Python-style for Python created attributes + const auto dot = name.find('.'); + if (dot == name.npos) { #ifdef PYBIND - // If attribute does not exist in C++, it might have been created in Python - auto it = mAttrs.find(name); - if (it == mAttrs.end()) { - auto itPy = mAttrsPy.find(name); - if (itPy != mAttrsPy.end()) { - return std::string(Py_TYPE(itPy->second.ptr())->tp_name); + // If attribute does not exist in C++, it might have been created in Python + auto it = mAttrs.find(name); + if (it == mAttrs.end()) { + auto itPy = mAttrsPy.find(name); + if (itPy != mAttrsPy.end()) { + return std::string(Py_TYPE(itPy->second.ptr())->tp_name); + } } - } #endif - return mAttrs.at(name).type().name(); + return mAttrs.at(name).type().name(); + } + else { + const auto ns = name.substr(0, dot); + const auto nsName = name.substr(dot + 1); + return future_std::any_cast<const DynamicAttributes&>(mAttrs.at(ns)).getAttrType(nsName); + } } std::set<std::string> getAttrsName() const override final { @@ -226,7 +307,24 @@ public: * The strategy here is to keep a copy of each attribute in py::object that is updated everytime. */ inline py::object getAttrPy(const std::string& name) const override final { - return mAttrsPy.at(name); + const auto dot = name.find('.'); + if (dot == name.npos) { + auto itPy = mAttrsPy.find(name); + if (itPy == mAttrsPy.end()) { + // Attribute may be a namespace + auto it = mAttrs.find(name); + AIDGE_ASSERT(it != mAttrs.end() && it->second.type() == typeid(DynamicAttributes), "get_attr(): attribute \"{}\" not found", name); + return py::cast(future_std::any_cast<const DynamicAttributes&>(it->second)); + } + else { + return itPy->second; + } + } + else { + const auto ns = name.substr(0, dot); + const auto nsName = name.substr(dot + 1); + return future_std::any_cast<const DynamicAttributes&>(mAttrs.at(ns)).getAttrPy(nsName); + } }; #endif diff --git a/include/aidge/utils/StaticAttributes.hpp b/include/aidge/utils/StaticAttributes.hpp index 8fc88ff79c50751ba7b79662fc9fc430d4ed601d..3bb41b5bb0d9c2727d95a2656a1a2d5b96ff950b 100644 --- a/include/aidge/utils/StaticAttributes.hpp +++ b/include/aidge/utils/StaticAttributes.hpp @@ -180,7 +180,6 @@ public: ////////////////////////////////////// // Runtime existance check with name bool hasAttr(const std::string& name) const override final { - AIDGE_ASSERT(isPascalCase(name), "Aidge standard requires PascalCase for C++ Attributes."); for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) { if (name == EnumStrings<ATTRS_ENUM>::data[i]) { return true; @@ -192,10 +191,8 @@ public: #ifdef PYBIND bool hasAttrPy(const std::string& name) const override final { - AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python, got '{}'.", name); - const std::string pascalName = snakeToPascal(name); for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) { - if (pascalName == EnumStrings<ATTRS_ENUM>::data[i]) { + if (name == EnumStrings<ATTRS_ENUM>::data[i]) { return true; } } @@ -234,7 +231,7 @@ public: static std::set<std::string> staticGetAttrsName() { std::set<std::string> attrsName; for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) { - attrsName.insert(pascalToSnake(std::string(EnumStrings<ATTRS_ENUM>::data[i]))); + attrsName.insert(std::string(EnumStrings<ATTRS_ENUM>::data[i])); } return attrsName; } @@ -244,10 +241,9 @@ public: if (name == "__dict__") { return py::none(); } - AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python, got '{}'.", name); - const std::string pascalName = snakeToPascal(name); + for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) { - if (pascalName == EnumStrings<ATTRS_ENUM>::data[i]) { + if (name == EnumStrings<ATTRS_ENUM>::data[i]) { // https://github.com/pybind/pybind11/blob/f3e0602802c7840992c97f4960515777cad6a5c7/include/pybind11/pytypes.h#L1119-L1138 // Normal accessor would not work as we convert the tuple to a py::object which can be anything return py::detail::accessor_policies::tuple_item::get(py::cast(mAttrs), static_cast<py::size_t>(i)); @@ -264,10 +260,8 @@ public: void setAttrPy(const std::string& name, py::object&& value) override final{ - AIDGE_ASSERT(isSnakeCase(name), "Aidge standard requires snake_case for Attributes with Python, got '{}'.", name); - const std::string pascalName = snakeToPascal(name); for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) { - if (pascalName == EnumStrings<ATTRS_ENUM>::data[i]) { + if (name == EnumStrings<ATTRS_ENUM>::data[i]) { // Cannot update attribute using reference has it would require templating // Use a dirty auto tmpAttr = py::cast(mAttrs); @@ -282,10 +276,9 @@ public: py::dict dict() const override { py::dict attributes; for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) { - const std::string snakeName = pascalToSnake(EnumStrings<ATTRS_ENUM>::data[i]); - // https://github.com/pybind/pybind11/blob/f3e0602802c7840992c97f4960515777cad6a5c7/include/pybind11/pytypes.h#L1119-L1138 - // Normal accessor would not work as we convert the tuple to a py::object which can be anything - attributes[snakeName.c_str()] = py::detail::accessor_policies::tuple_item::get(py::cast(mAttrs), static_cast<py::size_t>(i)); + // https://github.com/pybind/pybind11/blob/f3e0602802c7840992c97f4960515777cad6a5c7/include/pybind11/pytypes.h#L1119-L1138 + // Normal accessor would not work as we convert the tuple to a py::object which can be anything + attributes[EnumStrings<ATTRS_ENUM>::data[i]] = py::detail::accessor_policies::tuple_item::get(py::cast(mAttrs), static_cast<py::size_t>(i)); } return attributes; } @@ -306,8 +299,7 @@ public: // AttrDict get_a() const { // py::dict attributes_; // for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) { - // const std::string snakeName = pascalToSnake(std::string(EnumStrings<ATTRS_ENUM>::data[i])); - // attributes_[snakeName.c_str()] = py::detail::accessor_policies::tuple_item::get(py::cast(mAttrs), static_cast<py::size_t>(i)); + // attributes_[EnumStrings<ATTRS_ENUM>::data[i]] = py::detail::accessor_policies::tuple_item::get(py::cast(mAttrs), static_cast<py::size_t>(i)); // } // return AttrDict(attributes_); // } diff --git a/python_binding/operator/pybind_Split.cpp b/python_binding/operator/pybind_Split.cpp index 6efc123864f21bf8ea02008b29fe59f31685f17c..f63a01f9815aa59cfbad0aea36f148899f44c9ea 100644 --- a/python_binding/operator/pybind_Split.cpp +++ b/python_binding/operator/pybind_Split.cpp @@ -21,14 +21,13 @@ namespace py = pybind11; namespace Aidge { void init_Split(py::module& m) { - py::class_<Split_Op, std::shared_ptr<Split_Op>, Attributes, OperatorTensor>(m, "SplitOp", py::multiple_inheritance()) + py::class_<Split_Op, std::shared_ptr<Split_Op>, OperatorTensor>(m, "SplitOp", py::multiple_inheritance()) .def(py::init<DimSize_t, std::int8_t, std::vector<DimSize_t>&>(), py::arg("nb_outputs"), py::arg("axis"), py::arg("split")) .def_static("get_inputs_name", &Split_Op::getInputsName) - .def_static("get_outputs_name", &Split_Op::getOutputsName) - .def_static("attributes_name", &Split_Op::staticGetAttrsName); + .def_static("get_outputs_name", &Split_Op::getOutputsName); declare_registrable<Split_Op>(m, "SplitOp"); diff --git a/python_binding/utils/pybind_Attributes.cpp b/python_binding/utils/pybind_Attributes.cpp index 7f5dde63c4835eb694d5fd2d571d7c9c1fd5a9ac..bc0ccb3f4053e37c186acd919fcadae9d5d19a40 100644 --- a/python_binding/utils/pybind_Attributes.cpp +++ b/python_binding/utils/pybind_Attributes.cpp @@ -21,14 +21,17 @@ namespace Aidge { DynamicAttributes test_DynamicAttributes_binding() { DynamicAttributes attrs; - attrs.addAttr<int>("A", 42); - attrs.addAttr<std::string>("B", "test"); - attrs.addAttr<std::vector<bool>>("C", {true, false, true}); + attrs.addAttr<int>("a", 42); + attrs.addAttr<std::string>("b", "test"); + attrs.addAttr<std::vector<bool>>("c", {true, false, true}); + attrs.addAttr("mem.a", 1); + attrs.addAttr("mem.data.b", 1.0f); + attrs.addAttr("impl.c", std::string("test")); return attrs; } double test_DynamicAttributes_binding_check(DynamicAttributes& attrs) { - return attrs.getAttr<double>("D"); + return attrs.getAttr<double>("d"); } void init_Attributes(py::module& m){ @@ -44,6 +47,7 @@ void init_Attributes(py::module& m){ py::class_<DynamicAttributes, std::shared_ptr<DynamicAttributes>, Attributes>(m, "DynamicAttributes") + .def(py::init<>()) .def("add_attr", &DynamicAttributes::addAttrPy, py::arg("name"), py::arg("value")) .def("del_attr", &DynamicAttributes::delAttr, py::arg("name")); diff --git a/src/operator/Shape.cpp b/src/operator/Shape.cpp index 8166712e1e5fd967bb9328e95ecf8c5388636ba7..39f5e2fe09b7ac750b8ea9d48d17fc2e97013c1a 100644 --- a/src/operator/Shape.cpp +++ b/src/operator/Shape.cpp @@ -34,17 +34,17 @@ const std::string Aidge::Shape_Op::Type = "Shape"; bool Aidge::Shape_Op::forwardDims(bool /*allowDataDependency*/) { if (inputsAssociated()) { - if (mAttributes->template getAttr<std::int64_t>("Start") < 0) - mAttributes->template getAttr<std::int64_t>("Start") += static_cast<std::int64_t>(getInput(0)->nbDims()); - if (mAttributes->template getAttr<std::int64_t>("End") < 0) - mAttributes->template getAttr<std::int64_t>("End") += static_cast<std::int64_t>(getInput(0)->nbDims()); + if (this->start() < 0) + this->start() += static_cast<std::int64_t>(getInput(0)->nbDims()); + if (this->end() < 0) + this->end() += static_cast<std::int64_t>(getInput(0)->nbDims()); - const auto start = mAttributes->template getAttr<std::int64_t>("Start"); - const auto end = mAttributes->template getAttr<std::int64_t>("End"); + const auto start = this->start(); + const auto end = this->end(); const auto nbDims = static_cast<std::int64_t>(getInput(0)->nbDims()); const DimSize_t roi = end - start + 1; - AIDGE_ASSERT(start < nbDims && end < nbDims, "'Start' and 'End' must be < {}", nbDims); + AIDGE_ASSERT(start < nbDims && end < nbDims, "'start' and 'end' must be < {}", nbDims); AIDGE_ASSERT(roi> 1, "Unvalid ROI for Shape"); mOutputs[0]->resize({roi}); diff --git a/src/operator/Split.cpp b/src/operator/Split.cpp index 31de75e410afef98843d1f59a1221ecd3ba91832..af7474d8a21db9ece237440b46ecf57db9b270b4 100644 --- a/src/operator/Split.cpp +++ b/src/operator/Split.cpp @@ -28,8 +28,8 @@ void Aidge::Split_OpImpl::forward() { const Split_Op& op = dynamic_cast<const Split_Op&>(mOp); - const auto axis = op.template getAttr<std::int8_t>("Axis"); - const auto splits = op.template getAttr<std::vector<DimSize_t>>("Split"); + const auto axis = op.axis(); + const auto splits = op.split(); const auto dims = op.getInput(0)->dims(); //Compute pre/post axis strides @@ -68,7 +68,7 @@ bool Aidge::Split_Op::forwardDims(bool allowDataDependency) { if (inputsAssociated()) { // Copy optional input #1, if present, to attribute Split if (getInput(1)) { - if (!this->template getAttr<SplitAttr::Split>().empty()) { + if (!this->split().empty()) { Log::notice("Split_Op: ignoring non-empty Split attribute because input#1 takes precedence"); } @@ -78,21 +78,22 @@ bool Aidge::Split_Op::forwardDims(bool allowDataDependency) { } std::shared_ptr<Tensor> fallback; - this->template getAttr<SplitAttr::Split>().reserve(getInput(1)->size()); + this->split().clear(); // If both are provided input would override attrs + this->split().reserve(getInput(1)->size()); const auto& splits = getInput(1)->refCastFrom(fallback, NativeType<DimSize_t>::type, "cpu"); std::copy_n(static_cast<DimSize_t*>(splits.getImpl()->hostPtr()), splits.size(), - std::back_inserter(this->template getAttr<SplitAttr::Split>())); + std::back_inserter(this->split())); } // Compute output dims - if (this->template getAttr<std::int8_t>("Axis") < 0) - this->template getAttr<std::int8_t>("Axis") += static_cast<std::int8_t>(getInput(0)->nbDims()); + if (this->axis() < 0) + this->axis() += static_cast<std::int8_t>(getInput(0)->nbDims()); - DimSize_t dimToSplit = getInput(0)->dims()[this->template getAttr<std::int8_t>("Axis")]; + DimSize_t dimToSplit = getInput(0)->dims()[this->axis()]; DimSize_t nbOutput = this->nbOutputs(); // Fill Split attr if empty - if(this->template getAttr<SplitAttr::Split>().empty()) { + if(this->split().empty()) { // In case the input Split is not provided, divide the dimension of Axis into equal slices AIDGE_ASSERT(dimToSplit > nbOutput, "Split_Op: Output number {} musn't be bigger than dimension {}.", nbOutput, dimToSplit); DimSize_t baseSliceSize = dimToSplit / nbOutput; @@ -100,12 +101,12 @@ bool Aidge::Split_Op::forwardDims(bool allowDataDependency) { DimSize_t remainder = dimToSplit % nbOutput; for (DimSize_t i = 0; i < static_cast<DimSize_t>(nbOutput -1); ++i) { - this->template getAttr<SplitAttr::Split>().push_back(baseSliceSize); + this->split().push_back(baseSliceSize); } - this->template getAttr<SplitAttr::Split>().push_back(baseSliceSize + remainder); + this->split().push_back(baseSliceSize + remainder); } - const auto splits = this->template getAttr<SplitAttr::Split>(); + const auto splits = this->split(); AIDGE_ASSERT(splits.size() == nbOutput, "Split_Op: number of slices {} must be equal to number of outputs {}", splits, nbOutput); DimSize_t totalSplitSize = std::accumulate(splits.cbegin(), splits.cend(), 0); AIDGE_ASSERT(totalSplitSize == dimToSplit, "Split_Op: Total chunks size {} is different from dimension size {}.", totalSplitSize, dimToSplit); @@ -113,7 +114,7 @@ bool Aidge::Split_Op::forwardDims(bool allowDataDependency) { std::vector<DimSize_t> outDims = getInput(0)->dims(); for (std::size_t i = 0; i < nbOutput; ++i) { - outDims[this->template getAttr<std::int8_t>("Axis")] = this->template getAttr<SplitAttr::Split>()[i]; + outDims[this->axis()] = this->split()[i]; mOutputs[i]->resize(outDims); } diff --git a/src/utils/Attributes.cpp b/src/utils/Attributes.cpp deleted file mode 100644 index e79db53a60a955e3502e070cda5818d3d7b6c922..0000000000000000000000000000000000000000 --- a/src/utils/Attributes.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 CEA-List - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - * - ********************************************************************************/ - -#include "aidge/utils/Attributes.hpp" - -#include <cctype> // std::isdigit, std::islower, std::isupper, std::tolower, - // std::toupper -#include <string> - -std::string Aidge::Attributes::snakeToPascal(const std::string& snakeCase) { - std::string result; - bool to_upper = true; // Start with uppercase for PascalCase - - for (char ch : snakeCase) { - if (ch == '_') { - to_upper = true; // Next character should be uppercase - } else { - if (to_upper) { - result += std::toupper(ch); - to_upper = false; // Reset flag after making a character uppercase - } else { - result += ch; - } - } - } - return result; -} - -std::string Aidge::Attributes::pascalToSnake(const std::string& pascalCase) { - std::string result; - - for (char ch : pascalCase) { - if (std::isupper(ch)) { - if (!result.empty()) { - result += '_'; - } - result += std::tolower(ch); - } else { - result += ch; - } - } - return result; -} - -bool Aidge::Attributes::isPascalCase(const std::string& str) { - if (str.empty() || !std::isupper(str[0])) { - return false; - } - - bool expectUpper = false; - for (size_t i = 1; i < str.size(); ++i) { - if (str[i] == '_') { - return false; - } - if (std::isupper(str[i])) { - if (!expectUpper) { - return false; - } - expectUpper = false; - } else if (std::islower(str[i]) || std::isdigit(str[i])) { - expectUpper = true; - } else { - return false; - } - } - return true; -} - -bool Aidge::Attributes::isSnakeCase(const std::string& str) { - if (str.empty()) { - return false; - } - - bool lastCharWasUnderscore = false; - for (char ch : str) { - if (ch == '_') { - if (lastCharWasUnderscore) { - return false; - } - lastCharWasUnderscore = true; - } else if (!std::islower(ch) && !std::isdigit(ch)) { - return false; - } else { - lastCharWasUnderscore = false; - } - } - return true; -} diff --git a/unit_tests/utils/Test_DynamicAttributes.cpp b/unit_tests/utils/Test_DynamicAttributes.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b8a1264b3ad954e776a5ae4c47f03cd0c3fb82c9 --- /dev/null +++ b/unit_tests/utils/Test_DynamicAttributes.cpp @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2023 CEA-List + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + ********************************************************************************/ + +#include <catch2/catch_test_macros.hpp> + +#include <string> +#include <vector> + +#include "aidge/utils/DynamicAttributes.hpp" + +using namespace Aidge; + +TEST_CASE("[core/attributes] DynamicAttributes") { + SECTION("TestAttr") { + DynamicAttributes attrs; + attrs.addAttr("a", 1); + attrs.addAttr("b", 1.0f); + attrs.addAttr("c", std::string("test")); + attrs.addAttr<std::vector<bool>>("d", {false, true, false}); + + REQUIRE(attrs.getAttr<int>("a") == 1); + REQUIRE(attrs.getAttr<float>("b") == 1.0f); + REQUIRE(attrs.getAttr<std::string>("c") == "test"); + REQUIRE(attrs.getAttr<std::vector<bool>>("d") == std::vector<bool>{{false, true, false}}); + + attrs.addAttr("e", DynamicAttributes()); + attrs.getAttr<DynamicAttributes>("e").addAttr("e1", 1.0f); + attrs.getAttr<DynamicAttributes>("e").addAttr("e2", std::string("test")); + + REQUIRE(attrs.getAttr<DynamicAttributes>("e").getAttr<float>("e1") == 1.0f); + REQUIRE(attrs.getAttr<DynamicAttributes>("e").getAttr<std::string>("e2") == "test"); + } + + SECTION("TestAttrNS") { + DynamicAttributes attrs; + attrs.addAttr("mem.a", 1); + attrs.addAttr("mem.data.b", 1.0f); + attrs.addAttr("impl.c", std::string("test")); + attrs.addAttr<std::vector<bool>>("d", {false, true, false}); + + REQUIRE(attrs.getAttr<int>("mem.a") == 1); + REQUIRE(attrs.getAttr<float>("mem.data.b") == 1.0f); + REQUIRE(attrs.getAttr<std::string>("impl.c") == "test"); + REQUIRE(attrs.getAttr<std::vector<bool>>("d") == std::vector<bool>{{false, true, false}}); + + attrs.getAttr<DynamicAttributes>("mem.data").addAttr("e", 2.0f); + attrs.getAttr<DynamicAttributes>("impl").addAttr("f", std::string("test2")); + REQUIRE(attrs.getAttr<float>("mem.data.e") == 2.0f); + REQUIRE(attrs.getAttr<std::string>("impl.f") == "test2"); + + REQUIRE(attrs.getAttr<DynamicAttributes>("mem.data").getAttr<float>("b") == 1.0f); + REQUIRE(attrs.getAttr<DynamicAttributes>("impl").getAttr<std::string>("c") == "test"); + } +}