diff --git a/include/tensorwrapper/detail_/polymorphic_base.hpp b/include/tensorwrapper/detail_/polymorphic_base.hpp index a0e90b28..15e0ae0c 100644 --- a/include/tensorwrapper/detail_/polymorphic_base.hpp +++ b/include/tensorwrapper/detail_/polymorphic_base.hpp @@ -103,8 +103,13 @@ class PolymorphicBase { * @throw None No throw guarantee. */ bool are_equal(const_base_reference rhs) const noexcept { + // Downcast *this so it can be passed to are_equal_ const_base_reference plhs = static_cast(*this); - return are_equal_(rhs) && rhs.are_equal_(plhs); + + // This line is necessary if are_equal_ is overriden in BaseType + const PolymorphicBase& rhs_upcast = rhs; + + return are_equal_(rhs) && rhs_upcast.are_equal_(plhs); } /** @brief Determines if *this and @p rhs are polymorphically different. diff --git a/include/tensorwrapper/detail_/view_traits.hpp b/include/tensorwrapper/detail_/view_traits.hpp new file mode 100644 index 00000000..9deab3ac --- /dev/null +++ b/include/tensorwrapper/detail_/view_traits.hpp @@ -0,0 +1,61 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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. + */ + +#pragma once +#include + +namespace tensorwrapper::detail_ { + +/** @brief Is the cast from @p FromType to @p ToType just adding const? + * + * A common TMP pattern in implementing views is needing to convert mutable + * views to read-only views. This trait can be used to compare the template + * type parameters of two views (assuming the views are templated on what + * object they are acting like) in order to determine if they represent a + * conversion from @p FromType to @p ToType such that @p ToType is + * `const FromType`. If @p ToType is `const FromType` this template variable + * will be set to true, otherwise it will be set to false. + * + * @tparam FromType The type we are converting from. + * @tparam ToType The type we are converting to. + */ +template +constexpr bool is_mutable_to_immutable_cast_v = + !std::is_const_v && // FromType is NOT read-only + std::is_const_v && // ToType is read-only + std::is_same_v; // They differ by const-ness + +/** @brief Disables a templated function except when + * `is_mutable_to_immutable_cast_v` evaluates to true. + * + * If `View` is a template class with template parameter type `T`, we want the + * implicit conversion from `View` to `View` to exist. In practice, + * this leaves us with two options: partial specialization of `View` for + * const-qualified types or use of SFINAE to disable the conversion. We prefer + * the latter as the former requires us to duplicate the entirety of the + * class. This template type will disable the accompanying function via SFINAE + * if @p ToType is not `const FromType`. + * + * @tparam FromType The type we are converting from. Expected to be the + * template type parameter of the view we are casting from. + * @tparam ToType The type we are converting to. Expected to be the template + * type parameter of the view we are casting to. + */ +template +using enable_if_mutable_to_immutable_cast_t = + std::enable_if_t>; + +} // namespace tensorwrapper::detail_ \ No newline at end of file diff --git a/include/tensorwrapper/shape/shape_base.hpp b/include/tensorwrapper/shape/shape_base.hpp index 3c62e16d..ce352e61 100644 --- a/include/tensorwrapper/shape/shape_base.hpp +++ b/include/tensorwrapper/shape/shape_base.hpp @@ -18,6 +18,9 @@ #include #include #include +#include +#include + namespace tensorwrapper::shape { /** @brief Code factorization for the various types of shapes. @@ -34,19 +37,29 @@ namespace tensorwrapper::shape { * - get_rank_() * - get_size_() */ -class ShapeBase : public detail_::PolymorphicBase { +class ShapeBase : public tensorwrapper::detail_::PolymorphicBase { +private: + /// Type implementing the traits of this + using traits_type = ShapeTraits; + public: /// Type all shapes inherit from - using shape_base = ShapeBase; + using shape_base = typename traits_type::shape_base; /// Type of a pointer to the base of a shape object - using base_pointer = std::unique_ptr; + using base_pointer = typename traits_type::base_pointer; /// Type used to hold the rank of a tensor - using rank_type = unsigned short; + using rank_type = typename traits_type::rank_type; /// Type used to specify the number of elements in the shape - using size_type = std::size_t; + using size_type = typename traits_type::size_type; + + /// Type of an object acting like a mutable reference to a Smooth shape + using smooth_reference = SmoothView; + + /// Type of an object acting like a read-only reference to a Smooth shape + using const_smooth_reference = SmoothView; /// No-op for ShapeBase because ShapeBase has no state ShapeBase() noexcept = default; @@ -83,6 +96,34 @@ class ShapeBase : public detail_::PolymorphicBase { */ size_type size() const noexcept { return get_size_(); } + /** @brief Returns a view of *this as a Smooth object. + * + * It is possible to view any shape as a smooth shape. For more exotic + * shapes this may require flattening nestings and padding dimensions. + * This method ultimately dispatches to the as_smooth_ overload of the + * derived class to control how to smooth the shape out. + * + * @return A view of *this consistent with thinking of *this as a Smooth + * object. + * + * @throw std::bad_alloc if there is a problem allocating the view. Strong + * throw guarantee. + */ + smooth_reference as_smooth() { return as_smooth_(); } + + /** @brief Returns a read-only view of *this as a Smooth object. + * + * This method works the same as the non-const version except that the + * resulting view is read-only. + * + * @return A read-only view of *this consistent with thinking of *this as + * a Smooth object. + * + * @throw std::bad_alloc if there is a problem allocating the view. Strong + * throw guarantee. + */ + const_smooth_reference as_smooth() const { return as_smooth_(); } + protected: /** @brief Used to implement rank(). * @@ -108,6 +149,12 @@ class ShapeBase : public detail_::PolymorphicBase { * subject to a no-throw guarantee. */ virtual size_type get_size_() const noexcept = 0; + + /// Derived class should override to be consistent with as_smooth() + virtual smooth_reference as_smooth_() = 0; + + /// Derived class should override to be consistent with as_smooth() const + virtual const_smooth_reference as_smooth_() const = 0; }; } // namespace tensorwrapper::shape diff --git a/include/tensorwrapper/shape/shape_fwd.hpp b/include/tensorwrapper/shape/shape_fwd.hpp new file mode 100644 index 00000000..cc2657c5 --- /dev/null +++ b/include/tensorwrapper/shape/shape_fwd.hpp @@ -0,0 +1,34 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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. + */ + +#pragma once + +namespace tensorwrapper::shape { +namespace detail_ { + +template +class SmoothViewPIMPL; + +} + +class ShapeBase; + +class Smooth; + +template +class SmoothView; + +} // namespace tensorwrapper::shape \ No newline at end of file diff --git a/include/tensorwrapper/shape/shape_traits.hpp b/include/tensorwrapper/shape/shape_traits.hpp new file mode 100644 index 00000000..07dbc7a1 --- /dev/null +++ b/include/tensorwrapper/shape/shape_traits.hpp @@ -0,0 +1,72 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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. + */ + +#pragma once +#include +#include + +namespace tensorwrapper::shape { + +template +struct ShapeTraits; + +template<> +struct ShapeTraits { + using shape_base = ShapeBase; + using base_pointer = std::unique_ptr; + using rank_type = unsigned short; + using size_type = std::size_t; +}; + +template<> +struct ShapeTraits { + using shape_base = ShapeBase; + using base_pointer = std::unique_ptr; + using rank_type = unsigned short; + using size_type = std::size_t; +}; + +template<> +struct ShapeTraits : public ShapeTraits { + using value_type = Smooth; + using const_value_type = const value_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; +}; + +template<> +struct ShapeTraits : public ShapeTraits { + using value_type = Smooth; + using const_value_type = const value_type; + using reference = const value_type&; + using const_reference = const value_type&; + using pointer = const value_type*; + using const_pointer = const value_type*; +}; + +template +struct ShapeTraits> { + using smooth_traits = ShapeTraits; + using pimpl_type = detail_::SmoothViewPIMPL; + using const_pimpl_type = + detail_::SmoothViewPIMPL; + using pimpl_pointer = std::unique_ptr; + using const_pimpl_pointer = std::unique_ptr; +}; + +} // namespace tensorwrapper::shape \ No newline at end of file diff --git a/include/tensorwrapper/shape/smooth.hpp b/include/tensorwrapper/shape/smooth.hpp index fb4b8ad9..95315f2e 100644 --- a/include/tensorwrapper/shape/smooth.hpp +++ b/include/tensorwrapper/shape/smooth.hpp @@ -166,6 +166,12 @@ class Smooth : public ShapeBase { size_type(1), std::multiplies()); } + smooth_reference as_smooth_() override { return smooth_reference(*this); } + + virtual const_smooth_reference as_smooth_() const override { + return const_smooth_reference(*this); + } + /// Implements are_equal by calling ShapeBase::are_equal_impl_ bool are_equal_(const ShapeBase& rhs) const noexcept override { return are_equal_impl_(rhs); diff --git a/include/tensorwrapper/shape/smooth_view.hpp b/include/tensorwrapper/shape/smooth_view.hpp new file mode 100644 index 00000000..253efe08 --- /dev/null +++ b/include/tensorwrapper/shape/smooth_view.hpp @@ -0,0 +1,244 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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. + */ + +#pragma once +#include +#include + +namespace tensorwrapper::shape { + +/** @brief Wraps existing state in an API compatible with SmoothView. + * + * @tparam SmoothType Type of Smooth object *this is acting as. Expected to be + * either Smooth or const Smooth. + * + * Sometimes we have state which may not actually be in a Smooth object, but + * is capable of being used as a Smooth object. This class maps the Smooth + * API to the existing state. + */ +template +class SmoothView { +private: + /// Type of *this + using my_type = SmoothView; + + /// Type defining the traits for *this + using traits_type = ShapeTraits; + + /// Bind SmoothType for + template + using enable_if_mutable_to_immutable_cast_t = + tensorwrapper::detail_::enable_if_mutable_to_immutable_cast_t; + +public: + /// Types needed to implement Smooth's interface + ///@{ + using smooth_traits = typename traits_type::smooth_traits; + using smooth_type = typename smooth_traits::value_type; + using smooth_reference = typename smooth_traits::reference; + using const_smooth_reference = typename smooth_traits::const_reference; + using rank_type = typename smooth_traits::rank_type; + using size_type = typename smooth_traits::size_type; + ///@} + + /** @brief Creates a view of an existing Smooth object. + * + * In order to treat SmoothView objects on the same footing as Smooth + * objects it must be possible to implicitly convert between the two. + * This ctor will implicitly convert @p smooth into a SmoothView object. + * + * @param[in] smooth The object to convert. + * + * @throw std::bad_alloc if there is a problem allocating the PIMPL. + * Strong throw guarantee. + */ + SmoothView(smooth_reference smooth); + + /** @brief Implicitly converts mutable views into read-only views. + * + * @tparam SmoothType2 The type @p other is a view of. This method will + * only participate in overload resolution if + * SmoothType2 is `const Smooth`. + * @tparam Type parameter used to disable this method when + * SmoothType2 is not `const Smooth` and/or when + * SmoothType is not `Smooth`. + * + * Views act like references to an object. Views of mutable objects should + * be usable wherever views to read-only objects are used. This ctor + * enables the implicit conversion from mutable view to read-only view in + * order to make that possible. + * + * @param[in] other The view to convert to a read-only view. + * + * @throw std::bad_alloc if there is a problem allocating the PIMPL. Strong + * throw guarantee. + */ + template> + SmoothView(const SmoothView& other); + + /** @brief Creates a new view aliasing the same Smooth object as @p other. + * + * Views alias their state. The view constructed by this copy ctor will + * alias the same state that is aliased by @p other. In this sense it is + * a shallow copy of the aliased state and a deep copy of @p other. + * + * @param[in] other The view to copy. + * + * @throw std::bad_alloc if there is a problem allocating the copy. Strong + * throw guarantee. + */ + SmoothView(const SmoothView& other); + + /** @brief Creates a new view by taking the state of @p other. + * + * This ctor initializes *this by taking the state from @p other. After + * construction *this will alias the same object @p other did. It is worth + * noting the aliased object is untouched after this operation. + * + * @param[in,out] other The object to take the state from. After this + * operation @p other will be in a valid, but + * otherwise undefined state. + * + * @throw None No throw guarantee. + */ + SmoothView(SmoothView&& other) noexcept; + + /** @brief Overwrites *this to alias the same Smooth object as @p other. + * + * This operator causes the state in *this to instead alias the Smooth + * object in @p other. This does not release the state associated with the + * aliased object. + * + * @param[in] other The view to copy. + * + * @return *this after making it alias the state in @p other. + * + * @throw std::bad_alloc if there is a problem allocating the copy. Strong + * throw guarantee. + */ + SmoothView& operator=(const SmoothView& rhs); + + /** @brief Overrides the state of *this with the state of @p other. + * + * This operator causes the state to be replaced by the state in @p other. + * This does not release the state associated with the aliased object nor + * does it take state from the aliased object. + * + * @param[in,out] other The object to take the state from. After this + * operation @p other will be in a valid, but + * otherwise undefined state. + * + * @return *this after taking the state of @p other. + * + * @throw None No throw guarantee. + */ + SmoothView& operator=(SmoothView&& rhs) noexcept; + + /// Nothrow defaulted dtor + ~SmoothView() noexcept; + + /** @brief What is the extent of the i-th mode of the tensor with the + * aliased shape? + * + * @param[in] i The offset of the requested mode. @p i must be in the + * range [0, size()). + * + * @return The length of the @p i-th mode in a tensor with the aliased + * shape. + * + * @throw std::out_of_range if @p i is not in the range [0, size()). Strong + * throw guarantee. + */ + rank_type extent(size_type i) const; + + /** @brief What is the rank of the tensor the aliased shape describes? + * + * @return The rank of the tensor with the aliased shape. + * + * @throw None No throw guarantee. + */ + rank_type rank() const noexcept; + + /** @brief How many elements are in the tensor the aliased shape describes? + * + * @return The number of elements in a tensor with the aliased shape. + * + * @throw None No throw guarantee. + */ + size_type size() const noexcept; + + /// Swaps the state of *this with that of @p rhs + void swap(SmoothView& rhs) noexcept; + + /** @brief Is the Smooth shape aliased by *this the same as that aliased by + * @p rhs? + * + * Two SmoothView objects are value equal if the Smooth objects they alias + * compare value equal. + * + * @param[in] rhs The view aliasing the shape to compare to. + * + * @return True if *this aliases a Smooth object which is value equal to + * that aliased by @p rhs and false otherwise. + * + * @throw None No throw guarantee. + */ + bool operator==(const SmoothView& rhs) const noexcept; + + /** @brief Is *this different from @p rhs? + * + * @tparam SmoothType2 The type @p rhs is a view of. Expected to be Smooth + * or const Smooth. + * + * This method defines "different" as not value equal. See operator== for + * the definition of value equal. + * + * @param[in] rhs The view to compare to. + * + * @return False if *this is value equal to @p rhs and true otherwise. + * + * @throw None No throw guarantee. + */ + template + bool operator!=(const SmoothView& rhs) const noexcept { + return !((*this) == rhs); + } + +protected: + /// Lets the class access PIMPLs regardless of template type parameter + template + friend class SmoothView; + +private: + /// Type of a pointer to the PIMPL + using pimpl_pointer = typename traits_type::pimpl_pointer; + + /// Does *this have a PIMPL? + bool has_pimpl_() const noexcept; + + /// Makes a deep copy of the PIMPL + pimpl_pointer clone_() const; + + /// The object implementing *this + pimpl_pointer m_pimpl_; +}; + +extern template class SmoothView; +extern template class SmoothView; + +} // namespace tensorwrapper::shape \ No newline at end of file diff --git a/src/tensorwrapper/shape/detail_/smooth_alias.hpp b/src/tensorwrapper/shape/detail_/smooth_alias.hpp new file mode 100644 index 00000000..0bfc6f4a --- /dev/null +++ b/src/tensorwrapper/shape/detail_/smooth_alias.hpp @@ -0,0 +1,86 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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. + */ + +#pragma once +#include "smooth_view_pimpl.hpp" +#include + +namespace tensorwrapper::shape::detail_ { + +/** @brief Implements SmoothView by wrapping a Smooth object. + * + * A common scenario that occurs is that we need to use an actual Smooth + * object as if it were a SmoothView object. This class implements a SmoothView + * by wrapping a pointer to an actual Smooth object. All member functions of + * the Smooth object are simply forwarded through the SmoothView API. + * + * @tparam SmoothType the type of Smooth object *this is acting like a view of. + */ +template +class SmoothAlias : public SmoothViewPIMPL { +private: + /// Actual type *this inherits from + using my_base = SmoothViewPIMPL; + + /// Template type parameter with const-qualifier removed + using value_type = std::decay_t; + + /// Type of a SmoothAlias aliasing a read-only Smooth object. + using const_my_type = SmoothAlias; + +public: + /// Pull in bases's types + ///@{ + using base_type = typename my_base::base_type; + using base_pointer = typename my_base::base_pointer; + using parent_type = typename my_base::parent_type; + using smooth_pointer = typename parent_type::smooth_traits::pointer; + using smooth_reference = typename parent_type::smooth_reference; + using rank_type = typename my_base::rank_type; + using size_type = typename my_base::size_type; + using typename my_base::const_smooth_view_pimpl_pointer; + ///@} + + /// Aliases @p shape + explicit SmoothAlias(smooth_reference shape) : m_pshape_(&shape) {} + +protected: + /// Implemented by calling deep copy ctor + base_pointer clone_() const override { + return std::make_unique(*this); + } + + /// These just call shape's member function with the same name + ///@{ + rank_type extent_(size_type i) const override { return shape_().extent(i); } + rank_type rank_() const noexcept override { return shape_().rank(); } + size_type size_() const noexcept override { return shape_().size(); } + ///@} + + /// Implemented by by passing const reference of the shape *this aliases + const_smooth_view_pimpl_pointer as_const_() const override { + return std::make_unique(*m_pshape_); + } + +private: + /// Shortens the keystrokes for dereferencing m_pshape_ + decltype(auto) shape_() const { return *m_pshape_; } + + /// The Smooth object we are aliasing. + smooth_pointer m_pshape_; +}; + +} // namespace tensorwrapper::shape::detail_ \ No newline at end of file diff --git a/src/tensorwrapper/shape/detail_/smooth_view_pimpl.hpp b/src/tensorwrapper/shape/detail_/smooth_view_pimpl.hpp new file mode 100644 index 00000000..56a2b1e1 --- /dev/null +++ b/src/tensorwrapper/shape/detail_/smooth_view_pimpl.hpp @@ -0,0 +1,91 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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. + */ + +#pragma once +#include +#include + +namespace tensorwrapper::shape::detail_ { + +/** @brief Defines the API for all SmoothView PIMPLs. + * + * The data for a SmoothView can be laid out in a number of different ways. + * This class defines the API for accessing it. + * + * @tparam SmoothType The type *this will be a view of. + */ +template +class SmoothViewPIMPL : public tensorwrapper::detail_::PolymorphicBase< + SmoothViewPIMPL> { +private: + /// Type of *this + using my_type = SmoothViewPIMPL; + + /// Type *this actually inherits from. + using my_base = tensorwrapper::detail_::PolymorphicBase; + +public: + /// Type of the class *this is implementing + using parent_type = SmoothView; + + /// Pull in parent's types + ///@{ + using rank_type = typename parent_type::rank_type; + using size_type = typename parent_type::size_type; + ///@} + + /// Pull in base's types + using const_base_reference = typename my_base::const_base_reference; + + /// Type of a SmoothViewPIMPL if it aliases a const Smooth + using const_smooth_view_pimpl_pointer = + typename ShapeTraits::const_pimpl_pointer; + + /// Derived class implements by overriding extent_ + rank_type extent(size_type i) const { return extent_(i); } + + /// Derived class implements by overriding rank_ + rank_type rank() const noexcept { return rank_(); } + + /// Derived class implements by overriding size_ + size_type size() const noexcept { return size_(); } + + /// Derived class implements by overriding as_const_() + const_smooth_view_pimpl_pointer as_const() const { return as_const_(); } + +protected: + /// Derived class should implement to be consistent with SmoothView::extent + virtual rank_type extent_(size_type i) const = 0; + + /// Derived class should implement to be consistent with SmoothView::rank + virtual rank_type rank_() const noexcept = 0; + + /// Derived class should implement to be consistent with SmoothView::size + virtual size_type size_() const noexcept = 0; + + /// Used to create a PIMPL for SmoothView + virtual const_smooth_view_pimpl_pointer as_const_() const = 0; + + /// Compares state through common API of this class + bool are_equal_(const_base_reference rhs) const noexcept override { + if(rank() != rhs.rank()) return false; + for(size_type i = 0; i < rank(); ++i) + if(extent(i) != rhs.extent(i)) return false; + return true; + } +}; + +} // namespace tensorwrapper::shape::detail_ \ No newline at end of file diff --git a/src/tensorwrapper/shape/smooth_view.cpp b/src/tensorwrapper/shape/smooth_view.cpp new file mode 100644 index 00000000..fba74d12 --- /dev/null +++ b/src/tensorwrapper/shape/smooth_view.cpp @@ -0,0 +1,97 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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 "detail_/smooth_alias.hpp" +#include + +namespace tensorwrapper::shape { + +#define TPARAMS template +#define SMOOTH_VIEW SmoothView + +TPARAMS +SMOOTH_VIEW::SmoothView(smooth_reference smooth) : + m_pimpl_(std::make_unique>(smooth)) {} + +TPARAMS +template +SMOOTH_VIEW::SmoothView(const SmoothView& other) : + m_pimpl_(other.m_pimpl_->as_const()) {} + +TPARAMS +SMOOTH_VIEW::SmoothView(const SmoothView& other) : m_pimpl_(other.clone_()) {} + +TPARAMS +SMOOTH_VIEW::SmoothView(SmoothView&& other) noexcept = default; + +TPARAMS +SMOOTH_VIEW& SMOOTH_VIEW::operator=(const SmoothView& rhs) { + if(this != &rhs) SmoothView(rhs).swap(*this); + return *this; +} + +TPARAMS +SMOOTH_VIEW& SMOOTH_VIEW::operator=(SmoothView&& rhs) noexcept = default; + +TPARAMS +SMOOTH_VIEW::~SmoothView() noexcept = default; + +TPARAMS +typename SMOOTH_VIEW::rank_type SMOOTH_VIEW::extent(size_type i) const { + return m_pimpl_->extent(i); +} + +TPARAMS +typename SMOOTH_VIEW::rank_type SMOOTH_VIEW::rank() const noexcept { + return m_pimpl_->rank(); +} + +TPARAMS +typename SMOOTH_VIEW::size_type SMOOTH_VIEW::size() const noexcept { + return m_pimpl_->size(); +} + +TPARAMS +void SMOOTH_VIEW::swap(SmoothView& rhs) noexcept { + m_pimpl_.swap(rhs.m_pimpl_); +} + +TPARAMS +bool SMOOTH_VIEW::operator==( + const SmoothView& rhs) const noexcept { + if(has_pimpl_() != rhs.has_pimpl_()) return false; + if(!has_pimpl_()) return true; + return m_pimpl_->as_const()->are_equal(*rhs.m_pimpl_); +} + +TPARAMS +bool SMOOTH_VIEW::has_pimpl_() const noexcept { + return static_cast(m_pimpl_); +} + +TPARAMS +typename SMOOTH_VIEW::pimpl_pointer SMOOTH_VIEW::clone_() const { + return has_pimpl_() ? m_pimpl_->clone() : nullptr; +} + +#undef SMOOTH_VIEW +#undef TPARAMS + +template SmoothView::SmoothView(const SmoothView&); +template class SmoothView; +template class SmoothView; + +} // namespace tensorwrapper::shape \ No newline at end of file diff --git a/tests/cxx/unit_tests/tensorwrapper/detail_/view_traits.cpp b/tests/cxx/unit_tests/tensorwrapper/detail_/view_traits.cpp new file mode 100644 index 00000000..22699691 --- /dev/null +++ b/tests/cxx/unit_tests/tensorwrapper/detail_/view_traits.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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 "../helpers.hpp" +#include + +using namespace tensorwrapper::detail_; + +TEST_CASE("is_mutable_to_immutable_cast_v") { + // N.B. Only the const-ness of the types and whether they differ by + // const-ness should matter + STATIC_REQUIRE(is_mutable_to_immutable_cast_v); + + STATIC_REQUIRE_FALSE(is_mutable_to_immutable_cast_v); + STATIC_REQUIRE_FALSE(is_mutable_to_immutable_cast_v); + STATIC_REQUIRE_FALSE(is_mutable_to_immutable_cast_v); + STATIC_REQUIRE_FALSE( + is_mutable_to_immutable_cast_v); +} + +TEST_CASE("enable_if_mutable_to_immutable_cast_t") { + STATIC_REQUIRE( + std::is_same_v< + enable_if_mutable_to_immutable_cast_t, void>); +} \ No newline at end of file diff --git a/tests/cxx/unit_tests/tensorwrapper/shape/detail_/smooth_alias.cpp b/tests/cxx/unit_tests/tensorwrapper/shape/detail_/smooth_alias.cpp new file mode 100644 index 00000000..224c4812 --- /dev/null +++ b/tests/cxx/unit_tests/tensorwrapper/shape/detail_/smooth_alias.cpp @@ -0,0 +1,60 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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 "../../helpers.hpp" +#include + +using namespace tensorwrapper::shape; + +using types2test = std::pair; + +TEMPLATE_LIST_TEST_CASE("SmoothAlias", "", types2test) { + using pimpl_type = detail_::SmoothAlias; + std::decay_t scalar_shape{}, shape{1, 2, 3}; + + pimpl_type scalar(scalar_shape); + pimpl_type value(shape); + + SECTION("CTor") { + REQUIRE(scalar.rank() == scalar_shape.rank()); + REQUIRE(scalar.size() == scalar_shape.size()); + + REQUIRE(value.rank() == shape.rank()); + REQUIRE(value.size() == shape.size()); + } + + SECTION("clone") { + REQUIRE(scalar.clone()->are_equal(scalar)); + REQUIRE(value.clone()->are_equal(value)); + } + + SECTION("extent") { + REQUIRE_THROWS_AS(scalar.extent(0), std::out_of_range); + REQUIRE(value.extent(0) == 1); + REQUIRE(value.extent(1) == 2); + REQUIRE(value.extent(2) == 3); + } + + SECTION("rank") { + REQUIRE(scalar.rank() == 0); + REQUIRE(value.rank() == 3); + } + + SECTION("size") { + REQUIRE(scalar.size() == 1); + REQUIRE(value.size() == 6); + } +} \ No newline at end of file diff --git a/tests/cxx/unit_tests/tensorwrapper/shape/detail_/smooth_view_pimpl.cpp b/tests/cxx/unit_tests/tensorwrapper/shape/detail_/smooth_view_pimpl.cpp new file mode 100644 index 00000000..5c07e15b --- /dev/null +++ b/tests/cxx/unit_tests/tensorwrapper/shape/detail_/smooth_view_pimpl.cpp @@ -0,0 +1,56 @@ +/* + * Copyright 2024 NWChemEx-Project + * + * 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 "../../helpers.hpp" +#include + +/* Testing Strategy. + * + * At present the only thing actually implemented in SmoothViewPIMPL is + * are_equal so that's all this test case tests. + */ + +using namespace tensorwrapper::shape; + +using types2test = std::pair; + +TEMPLATE_LIST_TEST_CASE("SmoothViewPIMPL", "", types2test) { + using pimpl_type = detail_::SmoothAlias; + using shape_type = std::decay_t; + shape_type scalar_shape{}, shape{1, 2, 3}; + + pimpl_type scalar(scalar_shape); + pimpl_type value(shape); + + SECTION("are_equal") { + SECTION("Same") { + REQUIRE(scalar.are_equal(pimpl_type(scalar_shape))); + REQUIRE(value.are_equal(pimpl_type(shape))); + } + + SECTION("Different rank") { + shape_type rhs_shape{1}; + pimpl_type rhs(rhs_shape); + REQUIRE_FALSE(scalar.are_equal(rhs)); + } + + SECTION("Different extents") { + shape_type rhs_shape{2, 1, 3}; + pimpl_type rhs(rhs_shape); + REQUIRE_FALSE(value.are_equal(rhs)); + } + } +} \ No newline at end of file diff --git a/tests/cxx/unit_tests/tensorwrapper/shape/smooth.cpp b/tests/cxx/unit_tests/tensorwrapper/shape/smooth.cpp index 3ff31d25..9bdff9bd 100644 --- a/tests/cxx/unit_tests/tensorwrapper/shape/smooth.cpp +++ b/tests/cxx/unit_tests/tensorwrapper/shape/smooth.cpp @@ -94,6 +94,16 @@ TEST_CASE("Smooth") { REQUIRE(tensor.size() == size_type(60)); } + SECTION("as_smooth()") { + REQUIRE(scalar.as_smooth() == scalar); + REQUIRE(vector.as_smooth() == vector); + } + + SECTION("as_smooth() const") { + REQUIRE(std::as_const(scalar).as_smooth() == scalar); + REQUIRE(std::as_const(vector).as_smooth() == vector); + } + SECTION("are_equal_") { // Relies on operator==, which is tested below. So just spot check. REQUIRE(scalar.are_equal(Smooth{})); diff --git a/tests/cxx/unit_tests/tensorwrapper/shape/smooth_view.cpp b/tests/cxx/unit_tests/tensorwrapper/shape/smooth_view.cpp new file mode 100644 index 00000000..48587b86 --- /dev/null +++ b/tests/cxx/unit_tests/tensorwrapper/shape/smooth_view.cpp @@ -0,0 +1,130 @@ +/* + * Copyright 2024 NWChemEx Community + * + * 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 "../helpers.hpp" +#include +#include +#include + +using namespace tensorwrapper::testing; +using namespace tensorwrapper::shape; + +using rank_type = typename Smooth::rank_type; +using size_type = typename Smooth::size_type; + +using types2test = std::pair; + +TEMPLATE_LIST_TEST_CASE("SmoothView", "", types2test) { + using view_type = SmoothView; + using const_view_type = SmoothView>; + using smooth_type = typename view_type::smooth_type; + + smooth_type scalar{}; + smooth_type vector{3}; + + view_type alias_scalar(scalar); + view_type alias_vector(vector); + + SECTION("Ctors and assignment") { + SECTION("alias Smooth object") { + REQUIRE(alias_scalar.rank() == rank_type(0)); + REQUIRE(alias_scalar.size() == size_type(1)); + + REQUIRE(alias_vector.rank() == rank_type(1)); + REQUIRE(alias_vector.size() == size_type(3)); + } + + SECTION("Assign to const") { + // if TestType is non-const this tests mutable to const conversion, + // otherwise this duplicates the copy ctor test. + const_view_type const_scalar(alias_scalar); + REQUIRE(const_scalar.rank() == rank_type(0)); + REQUIRE(const_scalar.size() == size_type(1)); + } + + test_copy_and_move_ctors(alias_scalar, alias_vector); + + SECTION("copy assignment") { + view_type copy_scalar(alias_scalar); + auto pcopy_scalar = &(copy_scalar = alias_vector); + REQUIRE(copy_scalar == alias_vector); + REQUIRE(pcopy_scalar == ©_scalar); + } + + SECTION("move assignment") { + view_type copy_scalar(alias_scalar); + view_type copy_vector(alias_vector); + auto pcopy_scalar = &(copy_scalar = std::move(alias_vector)); + REQUIRE(copy_scalar == copy_vector); + REQUIRE(pcopy_scalar == ©_scalar); + } + } + + SECTION("extent") { + REQUIRE_THROWS_AS(alias_scalar.extent(0), std::out_of_range); + + REQUIRE(alias_vector.extent(0) == 3); + REQUIRE_THROWS_AS(alias_vector.extent(1), std::out_of_range); + } + + SECTION("rank") { + REQUIRE(alias_scalar.rank() == rank_type(0)); + REQUIRE(alias_vector.rank() == rank_type(1)); + } + + SECTION("size") { + REQUIRE(alias_scalar.size() == size_type(1)); + REQUIRE(alias_vector.size() == size_type(3)); + } + + SECTION("Utility methods") { + SECTION("swap") { + view_type scalar_copy(alias_scalar); + view_type vector_copy(alias_vector); + + alias_vector.swap(alias_scalar); + REQUIRE(alias_vector == scalar_copy); + REQUIRE(alias_scalar == vector_copy); + } + + SECTION("operator==") { + // Same shapes + REQUIRE(alias_scalar == view_type(scalar)); + REQUIRE(alias_vector == view_type(vector)); + + // (Possibly) different const-ness (if same const-ness duplicates + // the above check). Also check for symmetry. + REQUIRE(alias_scalar == const_view_type(alias_scalar)); + REQUIRE(const_view_type(alias_scalar) == alias_scalar); + + // Can compare aliases with objects + REQUIRE(alias_scalar == scalar); + + // Different ranks + REQUIRE_FALSE(alias_scalar == alias_vector); + + // Different extents + smooth_type vector2{2}; + REQUIRE_FALSE(alias_vector == view_type(vector2)); + } + + SECTION("operator!=") { + // Implemented by negating operator==, so just spot check + REQUIRE_FALSE(alias_scalar != view_type(scalar)); + REQUIRE(alias_scalar != alias_vector); + } + } +}