From c006be166914a0654fe1fab81ecfe21b83ae2c02 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Tue, 28 Jun 2016 13:49:03 -0400 Subject: [PATCH 01/29] Initial commit for ab-initio rewrite. --- .gitignore | 4 ++++ README.md | 11 +++++++++++ 2 files changed, 15 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..da15f6fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.dylib +*.o +*.os +*.so diff --git a/README.md b/README.md new file mode 100644 index 00000000..aa2d1618 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +This branch is a from-the-ground rewrite of the ndarray library, with the following goals: + + - Simplify the codebase and its dependencies by leveraging C++11/14 features. Ideally this will include removing Boost as a dependency (aside from optional Boost.Python bindings). + + - Support both Python 2 and Python 3 via pybind11, Boost.Python, and possibly Swig and Cython. + + - Remove internal expression template implementations in favor of delegating more work to Eigen (which may become a required dependency). + + - Add support for record dtypes and non-POD arrays. + +When the rewrite reaches a usable state, the current master version will be archived to a stable branch and the devel branch will become the new master. From 24fb164145b97132dcd54758cf496126757f1496 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sun, 10 Jul 2016 13:45:58 -0400 Subject: [PATCH 02/29] Add license file (BSD-like). --- LICENSE | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1b26ac3f --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2010-2018, Jim Bosch +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 727418d9fb4a5eb38b6ed1ce99526df01f4c5ade Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Tue, 3 Jul 2018 17:18:14 -0600 Subject: [PATCH 03/29] Add initial low-level classes, CMake build, tests. --- .gitignore | 1 + CMakeLists.txt | 34 ++ include/ndarray/common.hpp | 43 +++ include/ndarray/detail/ArrayImpl.hpp | 129 +++++++ include/ndarray/detail/IndexVectorTraits.hpp | 96 ++++++ include/ndarray/detail/Layout.hpp | 337 +++++++++++++++++++ include/ndarray/exceptions.hpp | 43 +++ tests/ArrayImpl.cpp | 122 +++++++ tests/Layout.cpp | 148 ++++++++ tests/main.cpp | 2 + 10 files changed, 955 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 include/ndarray/common.hpp create mode 100644 include/ndarray/detail/ArrayImpl.hpp create mode 100644 include/ndarray/detail/IndexVectorTraits.hpp create mode 100644 include/ndarray/detail/Layout.hpp create mode 100644 include/ndarray/exceptions.hpp create mode 100644 tests/ArrayImpl.cpp create mode 100644 tests/Layout.cpp create mode 100644 tests/main.cpp diff --git a/.gitignore b/.gitignore index da15f6fd..f00517fa 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.o *.os *.so +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..110143a0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.5) +project(ndarray VERSION 2.0.0 LANGUAGES CXX) + +find_package(fmt 4.1 REQUIRED) +find_package(Catch2 2.2.3 REQUIRED) + +add_library(ndarray INTERFACE) +target_include_directories(ndarray + INTERFACE + $ + $ +) +target_compile_features(ndarray + INTERFACE + cxx_auto_type + cxx_constexpr + cxx_deleted_functions + cxx_generic_lambdas + cxx_noexcept + cxx_static_assert + cxx_strong_enums +) +target_link_libraries(ndarray INTERFACE fmt::fmt) + +enable_testing() + +add_executable(tests + tests/main.cpp + tests/Layout.cpp + tests/ArrayImpl.cpp +) +target_link_libraries(tests Catch2::Catch2 ndarray) +include(Catch) +catch_discover_tests(tests) diff --git a/include/ndarray/common.hpp b/include/ndarray/common.hpp new file mode 100644 index 00000000..d6e11de8 --- /dev/null +++ b/include/ndarray/common.hpp @@ -0,0 +1,43 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_common_hpp_INCLUDED +#define NDARRAY_common_hpp_INCLUDED + +#include + +namespace ndarray { + +typedef std::uint8_t Byte; +typedef std::size_t Size; +typedef std::ptrdiff_t Offset; + +enum class MemoryOrder { + ROW_MAJOR, + COL_MAJOR +}; + +namespace detail { + +template class Layout; + +template struct ArrayImpl; + +template class OrderTag {}; +using RowMajorTag = OrderTag; +using ColMajorTag = OrderTag; + +template class TypeTag {}; + +} // namespace detail + +} // ndarray + +#endif // !NDARRAY_common_hpp_INCLUDED diff --git a/include/ndarray/detail/ArrayImpl.hpp b/include/ndarray/detail/ArrayImpl.hpp new file mode 100644 index 00000000..37909133 --- /dev/null +++ b/include/ndarray/detail/ArrayImpl.hpp @@ -0,0 +1,129 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2016, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_detail_ArrayImpl_hpp_INCLUDED +#define NDARRAY_detail_ArrayImpl_hpp_INCLUDED + +#include + +#include "ndarray/common.hpp" +#include "ndarray/detail/Layout.hpp" + +namespace ndarray { +namespace detail { + +template +struct ArrayImpl { + + constexpr static Size N = N_; + + ArrayImpl() noexcept = default; + + template + ArrayImpl( + ShapeVector const & shape, + MemoryOrder order, + TypeTag + ) : + buffer(nullptr), + layout(Layout::make(shape, sizeof(Element), order)) + { + std::shared_ptr tmp(new Element[layout->full_size()], std::default_delete()); + buffer = std::shared_ptr(tmp, reinterpret_cast(tmp.get())); + } + + template + ArrayImpl( + std::shared_ptr const & data, + ShapeVector const & shape, + MemoryOrder order + ) : + buffer(data, reinterpret_cast(data.get())), + layout(Layout::make(shape, sizeof(Element), order)) + {} + + template + ArrayImpl( + std::shared_ptr const & data, + ShapeVector const & shape, + StridesVector const & strides + ) : + buffer(data, reinterpret_cast(data.get())), + layout(Layout::make(shape, strides)) + {} + + template + ArrayImpl( + std::shared_ptr const & data, + std::shared_ptr const> layout_ + ) noexcept : + buffer(data, reinterpret_cast(data.get())), + layout(std::move(layout_)) + {} + + ArrayImpl( + std::shared_ptr buffer_, + std::shared_ptr const> layout_ + ) noexcept : + buffer(std::move(buffer_)), + layout(std::move(layout_)) + {} + + ArrayImpl(ArrayImpl const &) noexcept = default; + ArrayImpl(ArrayImpl &&) noexcept = default; + + ArrayImpl & operator=(ArrayImpl const &) noexcept = default; + ArrayImpl & operator=(ArrayImpl &&) noexcept = default; + + ~ArrayImpl() noexcept = default; + + template + Byte * index(IndexVector const & indices) const { + IndexVectorTraits::template check_dims(indices); + Size n = 0; + Offset offset = 0; + layout->for_each_dim( + [&indices, &n, &offset](auto const & d) { + assert(IndexVectorTraits::get_size(indices, n) < d.size()); + offset += IndexVectorTraits::get_size(indices, n)*d.stride(); + ++n; + } + ); + return buffer.get() + offset; + } + + void swap(ArrayImpl & other) noexcept { + buffer.swap(other); + layout.swap(other.layout); + } + + bool operator==(ArrayImpl const & other) const noexcept { + return buffer == other.buffer && + (layout == other.layout + || (layout && other.layout && *layout == *other.layout)); + } + + bool operator!=(ArrayImpl const & other) const noexcept { + return !(*this == other); + } + + std::shared_ptr buffer; + std::shared_ptr const> layout; +}; + +template +void swap(ArrayImpl & a, ArrayImpl & b) noexcept { + a.swap(b); +} + +} // namespace detail +} // namespace ndarray + +#endif // !NDARRAY_detail_ArrayImpl_hpp_INCLUDED diff --git a/include/ndarray/detail/IndexVectorTraits.hpp b/include/ndarray/detail/IndexVectorTraits.hpp new file mode 100644 index 00000000..1794867a --- /dev/null +++ b/include/ndarray/detail/IndexVectorTraits.hpp @@ -0,0 +1,96 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2016, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_detail_IndexVectorTraits_hpp_INCLUDED +#define NDARRAY_detail_IndexVectorTraits_hpp_INCLUDED + +#include +#include + +#include "ndarray/common.hpp" + +namespace ndarray { +namespace detail { + +template +struct IndexVectorTraits { + + static constexpr bool is_specialized = false; + +}; + +template +struct IndexVectorTraits> { + + static constexpr bool is_specialized = true; + + template + static void check_dims(std::array const & v) { + static_assert( + M == N, + "Index vector has wrong number of elements." + ); + } + + static Size get_size(std::array const & v, Size n) { + return v[n]; + } + + static Offset get_offset(std::array const & v, Size n) { + return v[n]; + } + +}; + +template +struct IndexVectorTraits> { + + static constexpr bool is_specialized = true; + + template + static void check_dims(std::initializer_list const & v) { + assert(M == v.size()); + } + + static Size get_size(std::initializer_list const & v, Size n) { + return v.begin()[n]; + } + + static Offset get_offset(std::initializer_list const & v, Size n) { + return v.begin()[n]; + } + +}; + +template +struct IndexVectorTraits> { + + static constexpr bool is_specialized = true; + + template + static void check_dims(std::vector const & v) { + assert(M == v.size()); + } + + static Size get_size(std::vector const & v, Size n) { + return v[n]; + } + + static Offset get_offset(std::vector const & v, Size n) { + return v[n]; + } + +}; + + +} // namespace detail +} // ndarray + +#endif // !NDARRAY_detail_IndexVectorTraits_hpp_INCLUDED \ No newline at end of file diff --git a/include/ndarray/detail/Layout.hpp b/include/ndarray/detail/Layout.hpp new file mode 100644 index 00000000..6eb27f7b --- /dev/null +++ b/include/ndarray/detail/Layout.hpp @@ -0,0 +1,337 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_detail_Layout_hpp_INCLUDED +#define NDARRAY_detail_Layout_hpp_INCLUDED + +#include + +#include "ndarray/common.hpp" +#include "ndarray/exceptions.hpp" +#include "ndarray/detail/IndexVectorTraits.hpp" + +namespace ndarray { +namespace detail { + +template <> class Layout<0>; + +// Storage for the shape and strides of an Array. +// +// Layout is a recursive container: Layout inherits from Layout, with +// 0-d specialized to break the recursion. Specializing 1-d would also have +// worked, but would require a bit more code duplication. +// +// Layouts are always held by shared_ptr. +// +// The dimensions of Layout are reversed relative to those of an Array: +// for a 3-d Array, the zeroth (outermost) Array dimension is held in a +// Layout<3>, while the last (innermost) dimension is held in a Layout<1>. +// The get_dim free functions can be used to obtain the Layout specialization +// for a particular dimension. +template +class Layout : public Layout { +public: + + using Base = Layout; + + // Public factory function from explicit shape and strides. + template + static std::shared_ptr make(ShapeVector const & shape, StridesVector const & strides) { + return std::make_shared(shape, strides); + } + + // Public factory function with explicit shape and automatic strides. + template + static std::shared_ptr make(ShapeVector const & shape, Size element_size, MemoryOrder order) { + switch (order) { + case MemoryOrder::ROW_MAJOR: + return std::make_shared(shape, element_size, RowMajorTag()); + case MemoryOrder::COL_MAJOR: + return std::make_shared(shape, element_size, ColMajorTag()); + default: + return nullptr; + } + } + + // Since Layouts are always passed/held by shared_ptr, an attempt to copy + // or move one is a mistake we'd like to catch. + Layout(Layout const &) = delete; + Layout(Layout &&) = delete; + + Layout & operator=(Layout const &) = delete; + Layout & operator=(Layout &&) = delete; + + ~Layout() noexcept = default; + + std::array shape() const noexcept { + std::array result; + fill_shape(result); + return result; + } + + std::array strides() const noexcept { + std::array result; + fill_strides(result); + return result; + } + + // Return the size of this dimension (in elements, not bytes). + Size size() const noexcept { return _size; } + + // Return the distance between elements in this dimension (in bytes). + Offset stride() const noexcept { return _stride; } + + // Return the combined size of this and all later (base class) dimensions. + Size full_size() const noexcept { return _size * Base::full_size(); } + + bool operator==(Layout const & other) const noexcept { + return _size == other._size && _stride == other._stride + && Base::operator==(other); + } + + bool operator!=(Layout const & other) const noexcept { + return !(*this == other); + } + + // Call the given function on this Layout and all nonzero base classes, + // starting with the outermost dimension (Layout) and ending with the + // innermost (Layout<1>). + template + void for_each_dim(F func) const { + func(*this); + Base::for_each_dim(func); + } + + // Call the given function on this Layout and all nonzero base classes, + // starting with the innermost dimension (Layout<0>) and ending with + // the outermost (Layout). + template + void for_each_dim_r(F func) const { + Base::for_each_dim_r(func); + func(*this); + } + + Size count_contiguous_dims(Size element_size, MemoryOrder order) const noexcept { + Size n_contiguous_dims = 0; + Offset contiguous_stride = element_size; + auto func = [&n_contiguous_dims, &contiguous_stride](auto const & layout) -> bool { + if (contiguous_stride == layout.stride()) { + ++n_contiguous_dims; + contiguous_stride *= layout.size(); + } else { + contiguous_stride = 0; // make sure the test never succeeds for any future dimension + } + }; + switch (order) { + case MemoryOrder::ROW_MAJOR: + for_each_dim_r(func); + break; + case MemoryOrder::COL_MAJOR: + for_each_dim(func); + break; + }; + return n_contiguous_dims; + } + + // Throw NoncontiguousError if this does not have at least C row-major + // contiguous dimensions (starting from the innermost) or, if C is negative + // at least -C column-major contiguous dimensions (starting from the + // outermost). + template + void check_contiguousness(Size element_size) const { + static_assert( + static_cast(N) >= C && -static_cast(N) <= C, + "Cannot have more contiguous dimensions than total dimensions." + ); + if (C == 0) return; + Size n_contiguous_dims = count_contiguous_dims( + element_size, + C > 0 ? MemoryOrder::ROW_MAJOR : MemoryOrder::COL_MAJOR + ); + if (std::abs(C) > n_contiguous_dims) { + throw NoncontiguousError(n_contiguous_dims, C); + } + } + +protected: + + // Explicit shape and strides, called recursively. + template + Layout( + ShapeVector const & shape, + StridesVector const & strides, + std::integral_constant + ) : + Base(shape, strides, std::integral_constant()), + _size(IndexVectorTraits::get_size(shape, M-N)), + _stride(IndexVectorTraits::get_offset(strides, M-N)) + {} + + // Compute row-major strides, called recursively. + template + Layout( + ShapeVector const & shape, + Size element_size, + std::integral_constant, + RowMajorTag + ) : + Base(shape, element_size, std::integral_constant(), RowMajorTag()), + _size(IndexVectorTraits::get_size(shape, M-N)), + _stride(Base::stride() * Base::size() * Base::last_stride(element_size)) + {} + + // Compute column-major strides, called recursively. + template + Layout( + ShapeVector const & shape, + Offset stride, + std::integral_constant, + ColMajorTag + ) : + Base( + shape, + stride * IndexVectorTraits::get_size(shape, M-N), + std::integral_constant(), + ColMajorTag() + ), + _size(IndexVectorTraits::get_size(shape, M-N)), + _stride(stride) + {} + + static Size last_stride(Size element_size) noexcept { return 1; } + + // Write the full array shape into the given array. + template + void fill_shape(std::array & shape) const { + static_assert(M >= N, "Layout not large enough to fill shape vector."); + shape[M-N] = _size; + Base::fill_shape(shape); + } + + // Write strides for all dimensions into the given array. + template + void fill_strides(std::array & strides) const { + static_assert(M >= N, "Layout not large enough to fill strides vector."); + strides[M-N] = _stride; + Base::fill_strides(strides); + } + +private: + + // Private inner class that inherits from Layout and has public ctors. + // This lets us use make_shared in Layout::make without making Layout's + // own constructors public. + class ConstructionHelper; + + Size _size; + Offset _stride; +}; + + +// Trivial private subclass of Layout, with public ctors usable by make_shared. +template +class Layout::ConstructionHelper : public Layout { +public: + + // Constructor from explicit shape and strides. + // Only called by Layout::make(). + template + ConstructionHelper(ShapeVector const & shape, StridesVector const & strides) : + Layout(shape, strides, std::integral_constant()) + {} + + // Constructor from shape and automatic strides. + // Only called by Layout::make(). + template + ConstructionHelper(ShapeVector const & shape, Size element_size, Tag) : + Layout(shape, element_size, std::integral_constant(), Tag()) + {} + +}; + + +// Empty 0-d specialization of Layout to break template recursion. +template <> +class Layout<0> { +public: + + Layout(Layout const &) = delete; + Layout(Layout &&) = delete; + Layout & operator=(Layout const &) = delete; + Layout & operator=(Layout &&) = delete; + + ~Layout() noexcept = default; + + // Return the size of this dimension (in elements, not bytes). + Size size() const noexcept { return 1; } + + // Return the distance between elements in this dimension (in bytes). + Offset stride() const noexcept { return 1; } + + // Return the combined size of this and all later (base class) dimensions. + Size full_size() const noexcept { return 1; } + + bool operator==(Layout const & other) const noexcept { return true; } + bool operator!=(Layout const & other) const noexcept { return false; } + + template + bool for_each_dim(F) const noexcept { return true; } + + template + bool for_each_dim_r(F) const noexcept { return true; } + +protected: + + // Explicit shape and strides. Only called by Layout<1>. + template + Layout( + ShapeVector const & shape, + StridesVector const & strides, + std::integral_constant + ) noexcept {} + + // Automatic strides. Only called by Layout<1>. + template + Layout( + ShapeVector const & shape, + Size element_size, + std::integral_constant, + Tag + ) noexcept {} + + static Size last_stride(Size element_size) noexcept { return element_size; } + + template + void fill_shape(std::array & shape) const noexcept {} + + template + void fill_strides(std::array & strides) const noexcept {} + +}; + + +// Retrieve the Layout dimension specialization corresponding to the Pth +// dimension out of N total dimensions. +template +inline Layout const & +get_dim(Layout const & layout) { return layout; } + + +// Retrieve the Layout dimension specialization corresponding to the Pth +// dimension out of N total dimensions. +template +inline std::shared_ptr const> +get_dim(std::shared_ptr const> const & layout) { return layout; } + + +} // namespace detail +} // ndarray + +#endif // !NDARRAY_detail_Layout_hpp_INCLUDED diff --git a/include/ndarray/exceptions.hpp b/include/ndarray/exceptions.hpp new file mode 100644 index 00000000..2182bd2a --- /dev/null +++ b/include/ndarray/exceptions.hpp @@ -0,0 +1,43 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_exceptions_hpp_INCLUDED +#define NDARRAY_exceptions_hpp_INCLUDED + +#include +#include "fmt/format.h" +#include "ndarray/common.hpp" + +namespace ndarray { + +class NoncontiguousError : public std::logic_error { + + static std::string format(Size actual, Offset required) { + static constexpr auto s = "Template parameters require at least {:d} {:s} " + "contiguous parameters; array only has {:d}"; + if (required > 0) { + return fmt::format(s, required, "row-major", actual); + } + return fmt::format(s, -required, "column-major", actual); + } + +public: + + explicit NoncontiguousError(char const * msg) : std::logic_error(msg) {} + + explicit NoncontiguousError(Size actual, Offset required) : + std::logic_error(format(actual, required)) + {} + +}; + +} // ndarray + +#endif // !NDARRAY_exceptions_hpp_INCLUDED diff --git a/tests/ArrayImpl.cpp b/tests/ArrayImpl.cpp new file mode 100644 index 00000000..d61ee514 --- /dev/null +++ b/tests/ArrayImpl.cpp @@ -0,0 +1,122 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#include +#include "catch2/catch.hpp" +#include "ndarray/detail/ArrayImpl.hpp" + +using namespace ndarray; + +namespace { + +struct CountedElement { + + static int next; + + static void reset() { next = 0; } + + CountedElement() : value(next++) {} + + int value; +}; + +int CountedElement::next = 0; + +template +void check_index(detail::ArrayImpl<2> const & a, std::initializer_list> const & b) { + auto shape = a.layout->shape(); + for (Size i = 0; i < shape[0]; ++i) { + for (Size j = 0; j < shape[1]; ++j) { + std::array indices = {i, j}; + T * v = reinterpret_cast(a.index(indices)); + REQUIRE(*v == b.begin()[i].begin()[j]); + } + } +} + +template +void check_comparisons(detail::ArrayImpl const & a, detail::ArrayImpl const & b) { + REQUIRE(a == a); + REQUIRE(b == b); + REQUIRE(a == detail::ArrayImpl(a)); + REQUIRE(b == detail::ArrayImpl(b)); + REQUIRE(a == detail::ArrayImpl(a.buffer, a.layout)); + REQUIRE(b == detail::ArrayImpl(b.buffer, b.layout)); + REQUIRE(a != detail::ArrayImpl()); + REQUIRE(b != detail::ArrayImpl()); + REQUIRE(a != detail::ArrayImpl(a.buffer, b.layout)); + REQUIRE(a != detail::ArrayImpl(b.buffer, a.layout)); + REQUIRE(b != detail::ArrayImpl(a.buffer, b.layout)); + REQUIRE(b != detail::ArrayImpl(b.buffer, a.layout)); +} + +void check_index_pair(detail::ArrayImpl<2> const & rm, detail::ArrayImpl<2> const & cm) { + check_index(rm, {{0, 1, 2}, + {3, 4, 5}}); + check_index(cm, {{0, 2, 4}, + {1, 3, 5}}); +} + +} // + +TEST_CASE("detail::ArrayImpl: construct and allocate", "[detail][ArrayImpl]") { + std::array shape = {2, 3}; + CountedElement::reset(); + detail::ArrayImpl<2> rm(shape, MemoryOrder::ROW_MAJOR, detail::TypeTag()); + CountedElement::reset(); + detail::ArrayImpl<2> cm(shape, MemoryOrder::COL_MAJOR, detail::TypeTag()); + check_index_pair(rm, cm); + check_comparisons(rm, cm); // requires buffers to differ, so we only run this test here +} + +TEST_CASE("detail::ArrayImpl: external data, automatic strides", "[detail][ArrayImpl]") { + std::array shape = {2, 3}; + CountedElement::reset(); + std::shared_ptr data(new CountedElement[6], std::default_delete()); + detail::ArrayImpl<2> rm(data, shape, MemoryOrder::ROW_MAJOR); + detail::ArrayImpl<2> cm(data, shape, MemoryOrder::COL_MAJOR); + check_index_pair(rm, cm); +} + +TEST_CASE("detail::ArrayImpl: external data, explicit strides", "[detail][ArrayImpl]") { + std::array shape = {2, 3}; + Offset s = sizeof(CountedElement); + std::array rm_strides = {s*3, s}; + std::array cm_strides = {s, s*2}; + CountedElement::reset(); + std::shared_ptr data(new CountedElement[6], std::default_delete()); + detail::ArrayImpl<2> rm(data, shape, rm_strides); + detail::ArrayImpl<2> cm(data, shape, cm_strides); + check_index_pair(rm, cm); +} + +TEST_CASE("detail::ArrayImpl: external data, Layout", "[detail][ArrayImpl]") { + std::array shape = {2, 3}; + auto rm_layout = detail::Layout<2>::make(shape, sizeof(CountedElement), MemoryOrder::ROW_MAJOR); + auto cm_layout = detail::Layout<2>::make(shape, sizeof(CountedElement), MemoryOrder::COL_MAJOR); + CountedElement::reset(); + std::shared_ptr data(new CountedElement[6], std::default_delete()); + detail::ArrayImpl<2> rm(data, rm_layout); + detail::ArrayImpl<2> cm(data, cm_layout); + check_index_pair(rm, cm); +} + +TEST_CASE("detail::ArrayImpl: external buffer, Layout", "[detail][ArrayImpl]") { + std::array shape = {2, 3}; + auto rm_layout = detail::Layout<2>::make(shape, sizeof(CountedElement), MemoryOrder::ROW_MAJOR); + auto cm_layout = detail::Layout<2>::make(shape, sizeof(CountedElement), MemoryOrder::COL_MAJOR); + CountedElement::reset(); + std::shared_ptr data(new CountedElement[6], std::default_delete()); + std::shared_ptr buffer(data, reinterpret_cast(data.get())); + detail::ArrayImpl<2> rm(buffer, rm_layout); + detail::ArrayImpl<2> cm(buffer, cm_layout); + check_index_pair(rm, cm); +} + diff --git a/tests/Layout.cpp b/tests/Layout.cpp new file mode 100644 index 00000000..7f4ce305 --- /dev/null +++ b/tests/Layout.cpp @@ -0,0 +1,148 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#include +#include "catch2/catch.hpp" +#include "ndarray/detail/Layout.hpp" + +using namespace ndarray; + +namespace { + +template +void check_get_dim( + detail::Layout const & layout, + std::array const & shape, + std::array const & strides, + std::integral_constant +) { + // empty specialization to break check_get_dim recursion (below) +} + +template +void check_get_dim( + detail::Layout const & layout, + std::array const & shape, + std::array const & strides, + std::integral_constant +) { + REQUIRE(detail::get_dim

(layout).size() == shape[P]); + REQUIRE(detail::get_dim

(layout).stride() == strides[P]); + check_get_dim(layout, shape, strides, std::integral_constant()); +} + +template +void check_layout( + detail::Layout const & layout, + std::array const & shape, + std::array const & strides +) { + REQUIRE(layout == *detail::Layout::make(shape, strides)); + REQUIRE(layout.shape() == shape); + REQUIRE(layout.strides() == strides); + REQUIRE(layout.full_size() == std::accumulate(shape.begin(), shape.end(), + static_cast(1), std::multiplies())); + Size i = 0; + layout.for_each_dim( + [&i, &shape, &strides](auto const & d) { + REQUIRE(d.size() == shape[i]); + REQUIRE(d.stride() == strides[i] ); + ++i; + return true; + } + ); + Size j = N - 1; + layout.for_each_dim_r( + [&j, &shape, &strides](auto const & d) { + REQUIRE(d.size() == shape[j]); + REQUIRE(d.stride() == strides[j]); + --j; + return true; + } + ); + check_get_dim(layout, shape, strides, std::integral_constant()); +} + +} // + +TEST_CASE("detail::Layout: automatic row-major contiguous strides", "[detail][Layout]") { + Size const element_size = 2; + std::array shape = {3, 4, 5}; + std::array strides = {40, 10, 2}; + auto layout = detail::Layout<3>::make(shape, element_size, MemoryOrder::ROW_MAJOR); + check_layout(*layout, shape, strides); + REQUIRE_NOTHROW(layout->check_contiguousness<3>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<2>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<1>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); + REQUIRE_THROWS_AS(layout->check_contiguousness<-1>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-2>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), NoncontiguousError); +} + +TEST_CASE("detail::Layout: explicit partially row-major strides", "[detail][Layout]") { + Size const element_size = 2; + std::array shape = {3, 4, 5}; + std::array strides = {80, 10, 2}; + auto layout = detail::Layout<3>::make(shape, strides); + check_layout(*layout, shape, strides); + REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), NoncontiguousError); + REQUIRE_NOTHROW(layout->check_contiguousness<2>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<1>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); + REQUIRE_THROWS_AS(layout->check_contiguousness<-1>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-2>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), NoncontiguousError); +} + +TEST_CASE("detail::Layout: automatic column-major contiguous strides", "[detail][Layout]") { + Size const element_size = 2; + std::array shape = {3, 4, 5}; + std::array strides = {2, 6, 24}; + auto layout = detail::Layout<3>::make(shape, element_size, MemoryOrder::COL_MAJOR); + check_layout(*layout, shape, strides); + REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<2>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<1>(element_size), NoncontiguousError); + REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<-1>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<-2>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<-3>(element_size)); +} + +TEST_CASE("detail::Layout: explicit partially column-major strides", "[detail][Layout]") { + Size const element_size = 2; + std::array shape = {3, 4, 5}; + std::array strides = {2, 6, 48}; + auto layout = detail::Layout<3>::make(shape, strides); + check_layout(*layout, shape, strides); + REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<2>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<1>(element_size), NoncontiguousError); + REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<-1>(element_size)); + REQUIRE_NOTHROW(layout->check_contiguousness<-2>(element_size)); + REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), NoncontiguousError); +} + +TEST_CASE("detail::Layout: explicit noncontiguous strides", "[detail][Layout]") { + Size const element_size = 2; + std::array shape = {3, 4, 5}; + std::array strides = {4, 60, 12}; + auto layout = detail::Layout<3>::make(shape, strides); + check_layout(*layout, shape, strides); + REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<2>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<1>(element_size), NoncontiguousError); + REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); + REQUIRE_THROWS_AS(layout->check_contiguousness<-1>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-2>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), NoncontiguousError); +} diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 00000000..62bf7476 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" From b6e10c4438e49c80063dd16e9bc334baac488756 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Wed, 4 Jul 2018 16:30:42 -0600 Subject: [PATCH 04/29] Update README to reflect current rewrite goals. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aa2d1618..4f6bbd5a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ This branch is a from-the-ground rewrite of the ndarray library, with the following goals: - - Simplify the codebase and its dependencies by leveraging C++11/14 features. Ideally this will include removing Boost as a dependency (aside from optional Boost.Python bindings). + - Simplify the codebase and its dependencies by leveraging C++11/14 features. Ideally this will include removing Boost as a dependency. - - Support both Python 2 and Python 3 via pybind11, Boost.Python, and possibly Swig and Cython. + - Support only Python 3 via pybind11. - - Remove internal expression template implementations in favor of delegating more work to Eigen (which may become a required dependency). + - Add support for non-POD arrays. - - Add support for record dtypes and non-POD arrays. + - Possibly remove internal expression template implementations in favor of delegating more work to Eigen (which may become a required dependency). When the rewrite reaches a usable state, the current master version will be archived to a stable branch and the devel branch will become the new master. From 6e1f644bd63cda9001f4f581aa0a70a151382b8d Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Wed, 4 Jul 2018 16:33:59 -0600 Subject: [PATCH 05/29] Add machinery for expected compile failure tests. --- CMakeLists.txt | 44 +++++- tests/__init__.py | 0 tests/compilation.py | 321 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/compilation.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 110143a0..3c1e96eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,11 @@ cmake_minimum_required(VERSION 3.5) project(ndarray VERSION 2.0.0 LANGUAGES CXX) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) # used to obtain build commands for expected compile failure tests + find_package(fmt 4.1 REQUIRED) find_package(Catch2 2.2.3 REQUIRED) +find_package(pybind11 2.2) add_library(ndarray INTERFACE) target_include_directories(ndarray @@ -24,11 +27,46 @@ target_link_libraries(ndarray INTERFACE fmt::fmt) enable_testing() -add_executable(tests +add_executable(tests_cpp tests/main.cpp tests/Layout.cpp tests/ArrayImpl.cpp ) -target_link_libraries(tests Catch2::Catch2 ndarray) +target_link_libraries(tests_cpp Catch2::Catch2 ndarray) include(Catch) -catch_discover_tests(tests) +catch_discover_tests(tests_cpp) + +if(pybind11_FOUND) + + function(ndarray_add_python_test_module MODULE_NAME) + configure_file(tests/${MODULE_NAME}.py tests/${MODULE_NAME}.py COPYONLY) + endfunction(ndarray_add_python_test_module) + + # TODO: use unittest discovery and TEST_INCLUDE_FILES to add finer-grained + # tests dynamically + + function(ndarray_add_python_test TEST_NAME) + set(FULL_TEST_NAME ${TEST_NAME}_Python) + ndarray_add_python_test_module(test_${TEST_NAME}) + add_test( + NAME + ${FULL_TEST_NAME} + COMMAND + ${PYTHON_EXECUTABLE} -m unittest tests.test_${TEST_NAME} + WORKING_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR} + ) + set_tests_properties( + ${FULL_TEST_NAME} + PROPERTIES LABELS "python;${TEST_NAME}" + ) + endfunction(ndarray_add_python_test) + + ndarray_add_python_test_module(__init__) + ndarray_add_python_test_module(compilation) + +else() + + message("pybind11 not found; not running Python tests.") + +endif() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/compilation.py b/tests/compilation.py new file mode 100644 index 00000000..e147a413 --- /dev/null +++ b/tests/compilation.py @@ -0,0 +1,321 @@ +# +# Copyright (c) 2010-2018, Jim Bosch +# All rights reserved. +# +# ndarray is distributed under a simple BSD-like license; +# see the LICENSE file that should be present in the root +# of the source distribution, or alternately available at: +# https://github.com/ndarray/ndarray +# + +import os +import io +import json +import inspect +import subprocess +import re +import textwrap + +__all__ = ("Compiler", "SnippetFormatter", "CompilationTestMixin") + + +COMPILE_COMMAND_CMAKE_TARGET = "tests/main.cpp" + +OUTPUT_DIR = "compile_tests" + + +class Compiler: + """An object that attempts to compile string code snippets. + + Parameters + ---------- + args : `list` + A command and command-line arguments, of the sort accepted as the + ``args`` argment to `subprocess.run`. + cwd : `str` + Working directory used to compile the target file. Should be passed + as the ``cwd`` argment to `subprocess.run`. + source_ext : `str` + Filename extension to use for source files. + output_ext : `str` + Filename extension to use for output files. + source_opt : `str` + Command-line option indicating the source file. + output_opt : `str` + Command-line option indicating the output file. + """ + def __init__(self, args, cwd, source_ext=".cpp", output_ext=".o", source_opt='-c', output_opt='-o'): + self.args = args + self.cwd = cwd + self.source_ext = source_ext + self.output_ext = output_ext + self.source_opt = source_opt + self.output_opt = output_opt + + @classmethod + def fromCMake(cls, filename, source_opt='-c', output_opt='-o'): + """Create a Compiler that uses CMake's compiler commands. + + This reads CMake's "compile_commands.json" (enabled with + CMAKE_EXPORT_COMPILE_COMMANDS, only available for Makefile or Ninja + builds), finds the source filename that ends with the given string (must + be unique), and removes the source and output arguments so they can be + specialized by __call__. + + Parameters + ---------- + filename : `str`, path-like + Name of a source file built by CMake whose build command we should + copy. + source_opt : `str` + Command-line option indicating the source file. + output_opt : `str` + Command-line option indicating the output file. + + Returns + ------- + compiler : `Compiler` + New Compiler instance. + """ + with open("compile_commands.json", "r") as f: + targets = json.load(f) + target, = [t for t in targets if t["file"].endswith(filename)] + args = [] + next_is_source = False + next_is_output = False + source_ext = "" + output_ext = ".o" + # discard any -o or -c arguments; we'll supply those ourselves + for arg in target["command"].split(): + if arg.startswith(output_opt): + if arg == output_opt: + next_is_output = True + _, output_ext = os.path.splitext(arg[2:]) + continue + if arg.startswith(source_opt): + if arg == source_opt: + next_is_source = True + _, source_ext = os.path.splitext(arg[2:]) + continue + if next_is_output: + next_is_output = False + _, output_ext = os.path.splitext(arg) + if next_is_source: + next_is_source = False + _, source_ext = os.path.splitext(arg) + continue + args.append(arg) + return cls(args=args, cwd=target["directory"], source_ext=source_ext, output_ext=output_ext, + source_opt=source_opt, output_opt=output_opt) + + def compile(self, name, code, output_dir=".", write_stderr=True, write_stdout=True): + """Attempt to compile the given code snippet. + + Parameters + ---------- + name : `str` + Name of the snippet, used to create source and output filenames. + code : `str` + Block of source code to compile. + output_dir : `str`, path-like + Directory for both source and output files. + write_stderr : `bool` + If True, write a stderr to [output_dir]/[name].stderr if it would be + non-empty. + write_stdout : `bool` + If True, write a stdout to [output_dir]/[name].stdout if it would be + non-empty. + + Returns + ------- + process : `subprocess.CompletedProcess` + A struct containing information about the compiler process, + including ``returncode`` (`int`), ``stdout`` (`str`), and + ``stderr`` (`str`). + output_file : `str`, path-like + Name of the output file that should have been generated if + the compiler was successful. + """ + source_file = os.path.join(output_dir, name + self.source_ext) + output_file = os.path.join(output_dir, name + self.output_ext) + if not os.path.isdir(os.path.dirname(source_file)): + os.makedirs(os.path.dirname(source_file)) + with open(source_file, 'w') as f: + f.write(code) + args = self.args + [self.source_opt, source_file, + self.output_opt, output_file] + process = subprocess.run(args, cwd=self.cwd, universal_newlines=True, stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + + if process.stderr and write_stderr: + with open(os.path.join(output_dir, name + ".stderr"), "w") as f: + f.write(process.stderr) + if process.stdout and write_stdout: + with open(os.path.join(output_dir, name + ".stdout"), "w") as f: + f.write(process.stdout) + return process, output_file + + +compiler = Compiler.fromCMake(COMPILE_COMMAND_CMAKE_TARGET) + + +class SnippetFormatter: + """A helper for formatting code snippets into compileable files. + + Parameters + ---------- + preamble : `str` or `bool` + A block of code to add to the beginning of every snippet. + Typically #includes, using declarations, and common test classes. + The preamble is always dedented to make it more natural to write + as a multi-line string. + prefix : `str` or `bool` + A block of code inserted after ``preamble`` and before ``code``. + Defaults to the opening of the main() function. + suffix : `str` or `bool` + A block of code inserted after ``code``. + Defaults to the opening closing brace of the main() function + started by ``prefix``. + indent : `str` or `bool` + Indentation to add to every line in ``code``. + base : `SnippetFormatter` + Another `SnippetFormatter` from which to inherit values. + If provided, passing `True` (the default) to any other argument + will cause that argument to be coped from ``base``. + """ + + def __init__(self, preamble=True, prefix=True, suffix=True, indent=True, base=None): + self.preamble = preamble + self.prefix = prefix + self.suffix = suffix + self.indent = indent + self._partial = tuple(base._partial) if base is not None else tuple() + + def inherit_arg(arg, default): + given = getattr(self, arg) + if given is True: + if base is not None: + setattr(self, arg, getattr(base, arg)) + else: + setattr(self, arg, default) + elif given is False or given is None: + setattr(self, arg, "") + + inherit_arg("preamble", "") + inherit_arg("prefix", "int main() {") + inherit_arg("indent", " ") + if isinstance(self.indent, int): + self.indent = self.indent * " " + inherit_arg("suffix", self.indent + "return 0;\n}") + self.preamble = textwrap.dedent(self.preamble) + + def __call__(self, code): + """Format the given code.""" + lines = list(self._partial) + lines.extend(textwrap.dedent(code).splitlines()) + b = io.StringIO() + b.write(self.preamble) + b.write("\n") + b.write(self.prefix) + b.write("\n") + for line in lines: + b.write(self.indent) + b.write(line) + b.write("\n") + b.write("\n") + b.write(self.suffix) + b.write("\n") + return b.getvalue() + + def partial(self, code): + r = SnippetFormatter(base=self) + r._partial += tuple(textwrap.dedent(code).splitlines()) + return r + + +def make_snippet_name(frame): + module, _ = os.path.splitext(os.path.basename(frame.filename)) + return "{}_{}_{}".format(module, frame.lineno, frame.function) + + +class CompilationTestMixin: + """A unittest.TestCase mixin for testing code compilation. + """ + + def assertCompiles(self, code, name=None, output_dir=None, formatter=None, **kwds): + """Assert that a code snippet does not compile. + + Parameters + ---------- + lines : `str` + Code to be compiled. + name : `str`, optional + Name of the snippet; used to generate filenames for source and + output files. If not provided, the calling method's + module, line number, and name are used to construct a name. + output_dir : `str`, path-like + Directory for source and output files. + formatter : callable, optional + A callable to apply to the code before compiling it (typically + a `SnippetFormatter`). If provided, the original code snippet + will be included in any test failure messages. + + Additional keyword arguments are passed to Compiler.compile. + """ + if name is None: + name = make_snippet_name(inspect.stack()[1]) + if output_dir is None: + output_dir = OUTPUT_DIR + if formatter is not None: + code = formatter(code) + process, _ = compiler.compile(name, code, output_dir=output_dir, **kwds) + self.assertEqual(process.returncode, 0, msg="{} did not compile".format(name)) + + def assertDoesNotCompile(self, code, name=None, stderr_regex=None, stdout_regex=None, + output_dir=None, formatter=None, **kwds): + """Assert that a code snippet does not compile. + + Parameters + ---------- + lines : `str` + Code to be compiled. + name : `str`, optional + Name of the snippet; used to generate filenames for source and + output files. If not provided, the calling method's + module, line number, and name are used to construct a name. + stderr_regex : `str` or compiled regular expression + A regular expression to search for in the compiler's STDERR. If + provided, failure to find this expression will also result in + an assertion failure. + stdout_regex : `str` or compiled regular expression + Like ``stderr_regex``, but applied to STDOUT. + output_dir : `str`, path-like + Directory for source and output files. + formatter : callable, optional + A callable to apply to the code before compiling it (typically + a `SnippetFormatter`). + + Additional keyword arguments are passed to Compiler.compile. + """ + if name is None: + name = make_snippet_name(inspect.stack()[1]) + if output_dir is None: + output_dir = OUTPUT_DIR + if formatter is not None: + code = formatter(code) + process, _ = compiler.compile(name, code, output_dir=output_dir, **kwds) + self.assertNotEqual(process.returncode, 0, msg="{} unexpectedly compiled".format(name)) + if stderr_regex is not None: + self.assertTrue( + re.search(stderr_regex, process.stderr) is not None, + msg="'{}' not found in stderr (see {}.stderr).".format( + stderr_regex, os.path.join(output_dir, name) + ) + ) + if stdout_regex is not None: + self.assertTrue( + re.search(stdout_regex, process.stdout) is not None, + msg="'{}' not found in stdout (see {}.stdout).".format( + stdout_regex, os.path.join(output_dir, name) + ) + ) From 878376bdfc449969ec859139fa45e4c08992eaac Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Thu, 5 Jul 2018 00:06:14 -0600 Subject: [PATCH 06/29] Start Array definitions and conversion tests. --- CMakeLists.txt | 1 + include/ndarray/Array.hpp | 77 ++++++++++++++++++++++++++++ tests/test_Array.py | 102 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 include/ndarray/Array.hpp create mode 100644 tests/test_Array.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c1e96eb..6f54e6c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ if(pybind11_FOUND) ndarray_add_python_test_module(__init__) ndarray_add_python_test_module(compilation) + ndarray_add_python_test(Array) else() diff --git a/include/ndarray/Array.hpp b/include/ndarray/Array.hpp new file mode 100644 index 00000000..0704bf63 --- /dev/null +++ b/include/ndarray/Array.hpp @@ -0,0 +1,77 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2016, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_ArrayBase_hpp_INCLUDED +#define NDARRAY_ArrayBase_hpp_INCLUDED + +#include "ndarray/common.hpp" +#include "ndarray/detail/ArrayImpl.hpp" + +namespace ndarray { + + namespace detail { + + constexpr bool contiguousness_convertible(Size n, Offset in, Offset out) { + return (in >= 0 && out >= 0 && in >= out) || + (in <= 0 && out <= 0 && in <= out) || + (n == 1 && (in == 1 || in == -1) && (out == 1 || out == -1)); + } + + template + using EnableIfConvertible = std::enable_if_t< + std::is_convertible::value && + contiguousness_convertible(N, C_in, C_out) + >; + + } // namespace detail + +template class Array; + +template +class Array { +public: + + Array() = default; + + template + Array(Array const & other) { + static_assert(std::is_convertible::value, "invalid pointer conversion"); + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } + + template + Array(Array && other) { + static_assert(std::is_convertible::value, "invalid pointer conversion"); + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } + +}; + +template +class Array { +public: + + Array() = default; + + template + Array(Array const & other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } + + template + Array(Array && other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } + +}; + +} // namespace ndarray + +#endif // !NDARRAY_ArrayBase_hpp_INCLUDED diff --git a/tests/test_Array.py b/tests/test_Array.py new file mode 100644 index 00000000..118b0383 --- /dev/null +++ b/tests/test_Array.py @@ -0,0 +1,102 @@ +# +# Copyright (c) 2010-2018, Jim Bosch +# All rights reserved. +# +# ndarray is distributed under a simple BSD-like license; +# see the LICENSE file that should be present in the root +# of the source distribution, or alternately available at: +# https://github.com/ndarray/ndarray +# + +import unittest +import itertools +from collections import namedtuple +from .compilation import CompilationTestMixin, SnippetFormatter + + +def load_tests(loader, tests, pattern): + suite = unittest.TestSuite() + suite.addTests(ArrayConversionTestCase.makeSuite1d()) + suite.addTests(ArrayConversionTestCase.makeSuite2d()) + return suite + + +class ArrayConversionTestCase(unittest.TestCase, CompilationTestMixin): + + formatter = SnippetFormatter( + """ + #include "ndarray/Array.hpp" + + using namespace ndarray; + + namespace { + + template + bool accept_array(Array const & a) { + return true; + } + + } // + """ + ) + + tmpl = """ + Array a; + accept_array(a); + """ + + ParameterTuple = namedtuple("ParameterTuple", ("t_in", "c_in", "t_out", "c_out")) + + @classmethod + def generate_combinations(cls, n): + for combo in itertools.product(("const", ""), tuple(range(-n, n + 1)), repeat=2): + yield cls.ParameterTuple._make(combo) + + def __init__(self, code, should_compile=None, stderr_regex=None, parameters=None): + if should_compile is None: + super().__init__() + else: + super().__init__("testCompiles" if should_compile else "testDoesNotCompile") + self.code = code + self.stderr_regex = stderr_regex + self.parameters = parameters + + def testCompiles(self): + with self.subTest(**self.parameters._asdict()): + self.assertCompiles(self.code, formatter=self.formatter) + + def testDoesNotCompile(self): + with self.subTest(**self.parameters._asdict()): + self.assertDoesNotCompile(self.code, stderr_regex=self.stderr_regex, formatter=self.formatter) + + @classmethod + def makeSuite1d(cls): + suite = unittest.TestSuite() + for p in cls.generate_combinations(n=1): + should_compile = True + stderr_regex = None + if p.c_in == 0 and p.c_out != 0: + should_compile = False + stderr_regex = "invalid contiguousness conversion" + if p.t_in == "const" and p.t_out != "const": + should_compile = False + stderr_regex = None + code = cls.tmpl.format(n=1, p=p) + suite.addTest(ArrayConversionTestCase(code, should_compile, stderr_regex, parameters=p)) + return suite + + @classmethod + def makeSuite2d(cls): + suite = unittest.TestSuite() + for p in cls.generate_combinations(n=2): + should_compile = True + stderr_regex = None + if (p.c_out > 0 and p.c_out > p.c_in) or (p.c_out < 0 and p.c_out < p.c_in): + should_compile = False + stderr_regex = "invalid contiguousness conversion" + if p.t_in == "const" and p.t_out != "const": + should_compile = False + stderr_regex = None + code = cls.tmpl.format(n=2, p=p) + suite.addTest(ArrayConversionTestCase(code, should_compile, stderr_regex, parameters=p)) + return suite From 30024d7e132da197fc1f847d2ff237b1e04bdf0c Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Fri, 6 Jul 2018 18:24:07 -0600 Subject: [PATCH 07/29] fixup! Add machinery for expected compile failure tests. --- tests/compilation.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/tests/compilation.py b/tests/compilation.py index e147a413..d5526011 100644 --- a/tests/compilation.py +++ b/tests/compilation.py @@ -159,7 +159,7 @@ def compile(self, name, code, output_dir=".", write_stderr=True, write_stdout=Tr compiler = Compiler.fromCMake(COMPILE_COMMAND_CMAKE_TARGET) -class SnippetFormatter: +class SnippetContext: """A helper for formatting code snippets into compileable files. Parameters @@ -189,7 +189,6 @@ def __init__(self, preamble=True, prefix=True, suffix=True, indent=True, base=No self.prefix = prefix self.suffix = suffix self.indent = indent - self._partial = tuple(base._partial) if base is not None else tuple() def inherit_arg(arg, default): given = getattr(self, arg) @@ -211,8 +210,7 @@ def inherit_arg(arg, default): def __call__(self, code): """Format the given code.""" - lines = list(self._partial) - lines.extend(textwrap.dedent(code).splitlines()) + lines = textwrap.dedent(code).splitlines() b = io.StringIO() b.write(self.preamble) b.write("\n") @@ -227,11 +225,6 @@ def __call__(self, code): b.write("\n") return b.getvalue() - def partial(self, code): - r = SnippetFormatter(base=self) - r._partial += tuple(textwrap.dedent(code).splitlines()) - return r - def make_snippet_name(frame): module, _ = os.path.splitext(os.path.basename(frame.filename)) @@ -242,7 +235,7 @@ class CompilationTestMixin: """A unittest.TestCase mixin for testing code compilation. """ - def assertCompiles(self, code, name=None, output_dir=None, formatter=None, **kwds): + def assertCompiles(self, code, name=None, output_dir=None, context=None, **kwds): """Assert that a code snippet does not compile. Parameters @@ -255,9 +248,9 @@ def assertCompiles(self, code, name=None, output_dir=None, formatter=None, **kwd module, line number, and name are used to construct a name. output_dir : `str`, path-like Directory for source and output files. - formatter : callable, optional + context : callable, optional A callable to apply to the code before compiling it (typically - a `SnippetFormatter`). If provided, the original code snippet + a `SnippetContext`). If provided, the original code snippet will be included in any test failure messages. Additional keyword arguments are passed to Compiler.compile. @@ -266,19 +259,20 @@ def assertCompiles(self, code, name=None, output_dir=None, formatter=None, **kwd name = make_snippet_name(inspect.stack()[1]) if output_dir is None: output_dir = OUTPUT_DIR - if formatter is not None: - code = formatter(code) + if context is not None: + code = context(code) process, _ = compiler.compile(name, code, output_dir=output_dir, **kwds) self.assertEqual(process.returncode, 0, msg="{} did not compile".format(name)) def assertDoesNotCompile(self, code, name=None, stderr_regex=None, stdout_regex=None, - output_dir=None, formatter=None, **kwds): + output_dir=None, context=None, **kwds): """Assert that a code snippet does not compile. Parameters ---------- - lines : `str` - Code to be compiled. + code : `str`, `list`, or `tuple` + Code to be compiled. If a list or tuple, elements will be joined + by newlines to form a string. name : `str`, optional Name of the snippet; used to generate filenames for source and output files. If not provided, the calling method's @@ -291,7 +285,7 @@ def assertDoesNotCompile(self, code, name=None, stderr_regex=None, stdout_regex= Like ``stderr_regex``, but applied to STDOUT. output_dir : `str`, path-like Directory for source and output files. - formatter : callable, optional + context : callable, optional A callable to apply to the code before compiling it (typically a `SnippetFormatter`). @@ -301,8 +295,10 @@ def assertDoesNotCompile(self, code, name=None, stderr_regex=None, stdout_regex= name = make_snippet_name(inspect.stack()[1]) if output_dir is None: output_dir = OUTPUT_DIR - if formatter is not None: - code = formatter(code) + if isinstance(code, (list, tuple)): + code = "\n".join(code) + if context is not None: + code = context(code) process, _ = compiler.compile(name, code, output_dir=output_dir, **kwds) self.assertNotEqual(process.returncode, 0, msg="{} unexpectedly compiled".format(name)) if stderr_regex is not None: From 023b6f7c5a723e94149971fde78c03bf944d4a8f Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Fri, 6 Jul 2018 18:24:39 -0600 Subject: [PATCH 08/29] fixup! Start Array definitions and conversion tests. --- tests/test_Array.py | 133 +++++++++++++++++++++++++------------------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/tests/test_Array.py b/tests/test_Array.py index 118b0383..1e8ba911 100644 --- a/tests/test_Array.py +++ b/tests/test_Array.py @@ -11,19 +11,18 @@ import unittest import itertools from collections import namedtuple -from .compilation import CompilationTestMixin, SnippetFormatter +from .compilation import CompilationTestMixin, SnippetContext def load_tests(loader, tests, pattern): suite = unittest.TestSuite() - suite.addTests(ArrayConversionTestCase.makeSuite1d()) - suite.addTests(ArrayConversionTestCase.makeSuite2d()) + suite.addTests(ArrayTestCase.makeSuite()) return suite -class ArrayConversionTestCase(unittest.TestCase, CompilationTestMixin): +class ArrayTestCase(unittest.TestCase, CompilationTestMixin): - formatter = SnippetFormatter( + context = SnippetContext( """ #include "ndarray/Array.hpp" @@ -32,7 +31,7 @@ class ArrayConversionTestCase(unittest.TestCase, CompilationTestMixin): namespace { template - bool accept_array(Array const & a) { + bool accept_Array(Array const & a) { return true; } @@ -40,63 +39,85 @@ class ArrayConversionTestCase(unittest.TestCase, CompilationTestMixin): """ ) - tmpl = """ - Array a; - accept_array(a); - """ + default_construct_template = "Array{p.ref}<{p.scalar} {p.const}, {p.n}, {p.c}> {var};" + accept_template = "accept_Array{p.ref}<{p.scalar} {p.const}, {p.n}, {p.c}>({var});" - ParameterTuple = namedtuple("ParameterTuple", ("t_in", "c_in", "t_out", "c_out")) + ParameterTuple = namedtuple("ParameterTuple", ("ref", "scalar", "const", "n", "c")) @classmethod - def generate_combinations(cls, n): - for combo in itertools.product(("const", ""), tuple(range(-n, n + 1)), repeat=2): - yield cls.ParameterTuple._make(combo) - - def __init__(self, code, should_compile=None, stderr_regex=None, parameters=None): - if should_compile is None: - super().__init__() - else: - super().__init__("testCompiles" if should_compile else "testDoesNotCompile") - self.code = code - self.stderr_regex = stderr_regex + def generate_parameters(cls, base=None, **kwds): + """Generate ParameterTuples from cartesian products of possible values. + + Parameters + ---------- + base : `ParameterTuple` + A tuple of parameter values or ranges to use as defaults. + + Keyword arguments with keys matching any of the fields in + ParameterTuple are accepted. THese may be scalar values or sequences + of values to include in the certesian product. "c" may have the + special value `range` (the built-in function), which generates "c" + values as `range(-n, n+1)` for every "n" value generated. + """ + param_range_list = [] + for field in cls.ParameterTuple._fields: + try: + v = kwds[field] + except KeyError: + assert base is not None + v = getattr(base, field) + if not isinstance(v, (list, tuple)): + v = (v,) + param_range_list.append(v) + for params in itertools.product(*param_range_list): + if params[-1] == range: + n = params[-2] + for c in range(-n, n + 1): + yield cls.ParameterTuple._make(params[:-1] + (c,)) + else: + yield cls.ParameterTuple._make(params) + + def __init__(self, method, parameters=None): + super().__init__(method) self.parameters = parameters - def testCompiles(self): - with self.subTest(**self.parameters._asdict()): - self.assertCompiles(self.code, formatter=self.formatter) - - def testDoesNotCompile(self): + def testContiguousConversions(self): + """Test that we can convert Arrays only when we do not increase + abs(C), and do not change the sign of C when C > 1. + """ + valid = [] + invalid = [] with self.subTest(**self.parameters._asdict()): - self.assertDoesNotCompile(self.code, stderr_regex=self.stderr_regex, formatter=self.formatter) - - @classmethod - def makeSuite1d(cls): - suite = unittest.TestSuite() - for p in cls.generate_combinations(n=1): - should_compile = True - stderr_regex = None - if p.c_in == 0 and p.c_out != 0: - should_compile = False - stderr_regex = "invalid contiguousness conversion" - if p.t_in == "const" and p.t_out != "const": - should_compile = False - stderr_regex = None - code = cls.tmpl.format(n=1, p=p) - suite.addTest(ArrayConversionTestCase(code, should_compile, stderr_regex, parameters=p)) - return suite + for out_params in self.generate_parameters(self.parameters, c=range): + if out_params.n == 1: + if self.parameters.c == 0 and out_params.c != 0: + invalid.append(out_params) + else: + valid.append(out_params) + else: + if out_params.c > 0 and out_params.c > self.parameters.c: + invalid.append(out_params) + elif out_params.c < 0 and out_params.c < self.parameters.c: + invalid.append(out_params) + else: + valid.append(out_params) + # Try compiling expected failures separately, since otherwise they'd hide each other. + for out_params in invalid: + with self.subTest(**out_params._asdict()): + self.assertDoesNotCompile( + [self.default_construct_template.format(p=self.parameters, var="a"), + self.accept_template.format(p=out_params, var="a")], + stderr_regex="invalid contiguousness conversion", + context=self.context + ) + # Compile expected successes together to save compile time. + lines = [self.default_construct_template.format(p=self.parameters, var="a")] + lines.extend(self.accept_template.format(p=out_params, var="a") for out_params in valid) + self.assertCompiles("\n".join(lines), context=self.context) @classmethod - def makeSuite2d(cls): + def makeSuite(cls): suite = unittest.TestSuite() - for p in cls.generate_combinations(n=2): - should_compile = True - stderr_regex = None - if (p.c_out > 0 and p.c_out > p.c_in) or (p.c_out < 0 and p.c_out < p.c_in): - should_compile = False - stderr_regex = "invalid contiguousness conversion" - if p.t_in == "const" and p.t_out != "const": - should_compile = False - stderr_regex = None - code = cls.tmpl.format(n=2, p=p) - suite.addTest(ArrayConversionTestCase(code, should_compile, stderr_regex, parameters=p)) + for p in cls.generate_parameters(ref="", scalar="float", const=("const", ""), n=(1, 2), c=range): + suite.addTest(ArrayTestCase("testContiguousConversions", parameters=p)) return suite From ce238f2ab49c4a5bc066c032dee942c5c18b5d5b Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sat, 11 Aug 2018 21:39:16 -0400 Subject: [PATCH 09/29] fixup! Add machinery for expected compile failure tests. --- tests/compilation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/compilation.py b/tests/compilation.py index d5526011..e03fbdf0 100644 --- a/tests/compilation.py +++ b/tests/compilation.py @@ -16,7 +16,7 @@ import re import textwrap -__all__ = ("Compiler", "SnippetFormatter", "CompilationTestMixin") +__all__ = ("Compiler", "SnippetContext", "CompilationTestMixin") COMPILE_COMMAND_CMAKE_TARGET = "tests/main.cpp" From 50d34973053949d2a6031c69a46d1e7f06ccea05 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sat, 11 Aug 2018 21:39:29 -0400 Subject: [PATCH 10/29] Move Array ctor definitions out of class declaration. --- include/ndarray/Array.hpp | 44 ++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/include/ndarray/Array.hpp b/include/ndarray/Array.hpp index 0704bf63..29b6b61b 100644 --- a/include/ndarray/Array.hpp +++ b/include/ndarray/Array.hpp @@ -41,16 +41,10 @@ class Array { Array() = default; template - Array(Array const & other) { - static_assert(std::is_convertible::value, "invalid pointer conversion"); - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } + Array(Array const & other); template - Array(Array && other) { - static_assert(std::is_convertible::value, "invalid pointer conversion"); - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } + Array(Array && other); }; @@ -61,17 +55,39 @@ class Array { Array() = default; template - Array(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } + Array(Array const & other); template - Array(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } + Array(Array && other); }; +template +template +Array::Array(Array const & other) { + static_assert(std::is_convertible::value, "invalid pointer conversion"); + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + +template +template +Array::Array(Array && other) { + static_assert(std::is_convertible::value, "invalid pointer conversion"); + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + +template +template +Array::Array(Array const & other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + +template +template +Array::Array(Array && other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + } // namespace ndarray #endif // !NDARRAY_ArrayBase_hpp_INCLUDED From 38adfe105b762627dd3b940d4b85f327593d76be Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sun, 12 Aug 2018 18:40:10 -0400 Subject: [PATCH 11/29] fixup! Add machinery for expected compile failure tests. --- tests/compilation.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/compilation.py b/tests/compilation.py index e03fbdf0..ee0d0763 100644 --- a/tests/compilation.py +++ b/tests/compilation.py @@ -240,8 +240,9 @@ def assertCompiles(self, code, name=None, output_dir=None, context=None, **kwds) Parameters ---------- - lines : `str` - Code to be compiled. + code: `str`, `list`, or `tuple` + Code to be compiled. If a list or tuple, elements will be joined + by newlines to form a string. name : `str`, optional Name of the snippet; used to generate filenames for source and output files. If not provided, the calling method's @@ -259,6 +260,8 @@ def assertCompiles(self, code, name=None, output_dir=None, context=None, **kwds) name = make_snippet_name(inspect.stack()[1]) if output_dir is None: output_dir = OUTPUT_DIR + if isinstance(code, (list, tuple)): + code = "\n".join(code) if context is not None: code = context(code) process, _ = compiler.compile(name, code, output_dir=output_dir, **kwds) From 995664d53757d981f14817313b823bf9cbc52b0b Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sun, 12 Aug 2018 18:41:03 -0400 Subject: [PATCH 12/29] Add ArrayRef. --- include/ndarray/Array.hpp | 134 +++++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 10 deletions(-) diff --git a/include/ndarray/Array.hpp b/include/ndarray/Array.hpp index 29b6b61b..18d93872 100644 --- a/include/ndarray/Array.hpp +++ b/include/ndarray/Array.hpp @@ -34,17 +34,33 @@ namespace ndarray { template class Array; +template class ArrayRef; + template class Array { public: Array() = default; - template - Array(Array const & other); + template + Array(Array const & other); + + template + Array(Array && other); + + template + Array(Array const & other); + + template + Array(Array && other); + + Array & operator=(Array const &) = default; + + Array & operator=(Array &&) = default; - template - Array(Array && other); + ArrayRef const deep() const; + + Array const shallow() const; }; @@ -60,22 +76,78 @@ class Array { template Array(Array && other); + Array & operator=(Array const &) = default; + + Array & operator=(Array &&) = default; + + ArrayRef const deep() const; + + Array const shallow() const; + }; template -template -Array::Array(Array const & other) { - static_assert(std::is_convertible::value, "invalid pointer conversion"); +class ArrayRef : public Array { +public: + + ArrayRef() = default; + + template + ArrayRef(Array const & other); + + template + ArrayRef(Array && other); + + template + ArrayRef(Array const & other); + + template + ArrayRef(Array && other); + +}; + +template +class ArrayRef : public Array { +public: + + ArrayRef() = default; + + template + ArrayRef(Array const & other); + + template + ArrayRef(Array && other); + +}; + +// Array from Array -------------------------------------------------- + +template +template +Array::Array(Array const & other) { static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); } template -template -Array::Array(Array && other) { - static_assert(std::is_convertible::value, "invalid pointer conversion"); +template +Array::Array(Array && other) { static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); } +template +template +Array::Array(Array const & other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + +template +template +Array::Array(Array && other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + +// Array from Array -------------------------------------------------------- + template template Array::Array(Array const & other) { @@ -88,6 +160,48 @@ Array::Array(Array && other) { static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); } + +// ArrayRef from Array ----------------------------------------------- + +template +template +ArrayRef::ArrayRef(Array const & other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + +template +template +ArrayRef::ArrayRef(Array && other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + +template +template +ArrayRef::ArrayRef(Array const & other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + +template +template +ArrayRef::ArrayRef(Array && other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + + +// ArrayRef from Array ----------------------------------------------------- + +template +template +ArrayRef::ArrayRef(Array const & other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + +template +template +ArrayRef::ArrayRef(Array && other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); +} + } // namespace ndarray #endif // !NDARRAY_ArrayBase_hpp_INCLUDED From 8cb6c8ac77515dd2b044b1fafb67308c5f21498f Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sun, 12 Aug 2018 19:33:12 -0400 Subject: [PATCH 13/29] Version 1 ArrayRef conversions all working. --- include/ndarray/Array.hpp | 158 +++++++++++--------------------------- 1 file changed, 45 insertions(+), 113 deletions(-) diff --git a/include/ndarray/Array.hpp b/include/ndarray/Array.hpp index 18d93872..8c1b94ec 100644 --- a/include/ndarray/Array.hpp +++ b/include/ndarray/Array.hpp @@ -16,21 +16,15 @@ namespace ndarray { - namespace detail { +namespace detail { - constexpr bool contiguousness_convertible(Size n, Offset in, Offset out) { - return (in >= 0 && out >= 0 && in >= out) || - (in <= 0 && out <= 0 && in <= out) || - (n == 1 && (in == 1 || in == -1) && (out == 1 || out == -1)); - } - - template - using EnableIfConvertible = std::enable_if_t< - std::is_convertible::value && - contiguousness_convertible(N, C_in, C_out) - >; +constexpr bool contiguousness_convertible(Size n, Offset in, Offset out) { + return (in >= 0 && out >= 0 && in >= out) || + (in <= 0 && out <= 0 && in <= out) || + (n == 1 && (in == 1 || in == -1) && (out == 1 || out == -1)); +} - } // namespace detail +} // namespace detail template class Array; @@ -43,25 +37,29 @@ class Array { Array() = default; template - Array(Array const & other); + Array(Array const & other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } template - Array(Array && other); + Array(Array && other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } template - Array(Array const & other); + Array(Array const & other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } template - Array(Array && other); + Array(Array && other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } Array & operator=(Array const &) = default; Array & operator=(Array &&) = default; - ArrayRef const deep() const; - - Array const shallow() const; - }; template @@ -71,136 +69,70 @@ class Array { Array() = default; template - Array(Array const & other); + Array(Array const & other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } template - Array(Array && other); + Array(Array && other) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } Array & operator=(Array const &) = default; Array & operator=(Array &&) = default; - ArrayRef const deep() const; - - Array const shallow() const; - }; template class ArrayRef : public Array { + using Base = Array; public: ArrayRef() = default; template - ArrayRef(Array const & other); + ArrayRef(Array const & other) : Base(other) {} template - ArrayRef(Array && other); + ArrayRef(Array && other) : Base(std::move(other)) {} template - ArrayRef(Array const & other); + ArrayRef(Array const & other) : Base(other) {} template - ArrayRef(Array && other); + ArrayRef(Array && other) : Base(std::move(other)) {} + + ArrayRef & operator=(ArrayRef const &) = delete; + + ArrayRef & operator=(ArrayRef &&) = delete; + + template + ArrayRef const & operator=(Array const & other) const = delete; }; template class ArrayRef : public Array { + using Base = Array; public: ArrayRef() = default; template - ArrayRef(Array const & other); + ArrayRef(Array const & other) : Base(other) {} template - ArrayRef(Array && other); - -}; - -// Array from Array -------------------------------------------------- - -template -template -Array::Array(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} + ArrayRef(Array && other) : Base(std::move(other)) {} -template -template -Array::Array(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} + ArrayRef const & operator=(ArrayRef const &) const; -template -template -Array::Array(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} - -template -template -Array::Array(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} + ArrayRef const & operator=(ArrayRef &&) const; -// Array from Array -------------------------------------------------------- + template + ArrayRef const & operator=(Array const & other) const; -template -template -Array::Array(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} - -template -template -Array::Array(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} - - -// ArrayRef from Array ----------------------------------------------- - -template -template -ArrayRef::ArrayRef(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} - -template -template -ArrayRef::ArrayRef(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} - -template -template -ArrayRef::ArrayRef(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} - -template -template -ArrayRef::ArrayRef(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} - - -// ArrayRef from Array ----------------------------------------------------- - -template -template -ArrayRef::ArrayRef(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} - -template -template -ArrayRef::ArrayRef(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); -} +}; } // namespace ndarray From fcac651cfb5793b8247ab463d6a32c7208a12e40 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sun, 12 Aug 2018 20:12:48 -0400 Subject: [PATCH 14/29] Switch to Array + Deref model. --- include/ndarray/Array.hpp | 125 +++++++++++++++++++++++--------------- tests/test_Array.py | 113 +++++++++++++++++++++------------- 2 files changed, 147 insertions(+), 91 deletions(-) diff --git a/include/ndarray/Array.hpp b/include/ndarray/Array.hpp index 8c1b94ec..265ea3bb 100644 --- a/include/ndarray/Array.hpp +++ b/include/ndarray/Array.hpp @@ -1,6 +1,6 @@ // -*- c++ -*- /* - * Copyright (c) 2010-2016, Jim Bosch + * Copyright (c) 2010-2018, Jim Bosch * All rights reserved. * * ndarray is distributed under a simple BSD-like license; @@ -24,113 +24,138 @@ constexpr bool contiguousness_convertible(Size n, Offset in, Offset out) { (n == 1 && (in == 1 || in == -1) && (out == 1 || out == -1)); } +constexpr Offset nested_contiguousness(Size n, Offset c) { + return (c > 0 && c == n) ? c - 1 : (c < 0 && -c == n) ? c + 1 : c; +} + } // namespace detail template class Array; -template class ArrayRef; +template class Deref; -template -class Array { +template +class Array { public: - Array() = default; + using Reference = T const &; - template - Array(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } + Array() noexcept = default; template - Array(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + Array(Array const & other) noexcept { + static_assert(detail::contiguousness_convertible(1, D, C), "invalid contiguousness conversion"); } template - Array(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + Array(Array && other) noexcept { + static_assert(detail::contiguousness_convertible(1, D, C), "invalid contiguousness conversion"); } - template - Array(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } + Array & operator=(Array const &) noexcept = default; + + Array & operator=(Array &&) noexcept = default; - Array & operator=(Array const &) = default; + Deref> operator*() const noexcept; - Array & operator=(Array &&) = default; + Reference operator[](Size n) const; +protected: + detail::ArrayImpl<1> _impl; }; -template -class Array { +template +class Array : public Array { public: - Array() = default; + using Reference = T &; + + Array() noexcept = default; template - Array(Array const & other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + Array(Array const & other) noexcept { + static_assert(detail::contiguousness_convertible(1, D, C), "invalid contiguousness conversion"); } template - Array(Array && other) { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + Array(Array && other) noexcept { + static_assert(detail::contiguousness_convertible(1, D, C), "invalid contiguousness conversion"); } - Array & operator=(Array const &) = default; + Array & operator=(Array const &) noexcept = default; + + Array & operator=(Array &&) noexcept = default; + + Deref> operator*() const noexcept; - Array & operator=(Array &&) = default; + Reference operator[](Size n) const; }; + template -class ArrayRef : public Array { - using Base = Array; +class Array { public: - ArrayRef() = default; + using Reference = Array; - template - ArrayRef(Array const & other) : Base(other) {} + Array() noexcept = default; - template - ArrayRef(Array && other) : Base(std::move(other)) {} + Array(Array const &) noexcept = default; + + Array(Array &&) noexcept = default; template - ArrayRef(Array const & other) : Base(other) {} + Array(Array const & other) noexcept { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } template - ArrayRef(Array && other) : Base(std::move(other)) {} + Array(Array && other) noexcept { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } - ArrayRef & operator=(ArrayRef const &) = delete; + Array & operator=(Array const &) noexcept = default; - ArrayRef & operator=(ArrayRef &&) = delete; + Array & operator=(Array &&) noexcept = default; - template - ArrayRef const & operator=(Array const & other) const = delete; + Deref> operator*() const noexcept; + Reference operator[](Size n) const; + +protected: + detail::ArrayImpl<1> _impl; }; template -class ArrayRef : public Array { - using Base = Array; +class Array : public Array { public: - ArrayRef() = default; + using Reference = Array; + + Array() noexcept = default; + + Array(Array const &) noexcept = default; + + Array(Array &&) noexcept = default; template - ArrayRef(Array const & other) : Base(other) {} + Array(Array const & other) noexcept { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } template - ArrayRef(Array && other) : Base(std::move(other)) {} + Array(Array && other) noexcept { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); + } + + Array & operator=(Array const &) noexcept = default; - ArrayRef const & operator=(ArrayRef const &) const; + Array & operator=(Array &&) noexcept = default; - ArrayRef const & operator=(ArrayRef &&) const; + Deref> operator*() const noexcept; - template - ArrayRef const & operator=(Array const & other) const; + Reference operator[](Size n) const; }; diff --git a/tests/test_Array.py b/tests/test_Array.py index 1e8ba911..da422c4e 100644 --- a/tests/test_Array.py +++ b/tests/test_Array.py @@ -20,32 +20,12 @@ def load_tests(loader, tests, pattern): return suite -class ArrayTestCase(unittest.TestCase, CompilationTestMixin): - - context = SnippetContext( - """ - #include "ndarray/Array.hpp" - - using namespace ndarray; +class ParameterTuple(namedtuple("ParameterTuple", ("scalar", "const", "n", "c"))): - namespace { - - template - bool accept_Array(Array const & a) { - return true; - } - - } // - """ - ) - - default_construct_template = "Array{p.ref}<{p.scalar} {p.const}, {p.n}, {p.c}> {var};" - accept_template = "accept_Array{p.ref}<{p.scalar} {p.const}, {p.n}, {p.c}>({var});" - - ParameterTuple = namedtuple("ParameterTuple", ("ref", "scalar", "const", "n", "c")) + template = "<{scalar} {const}, {n}, {c}>" @classmethod - def generate_parameters(cls, base=None, **kwds): + def generate(cls, base=None, **kwds): """Generate ParameterTuples from cartesian products of possible values. Parameters @@ -60,7 +40,7 @@ def generate_parameters(cls, base=None, **kwds): values as `range(-n, n+1)` for every "n" value generated. """ param_range_list = [] - for field in cls.ParameterTuple._fields: + for field in cls._fields: try: v = kwds[field] except KeyError: @@ -73,14 +53,52 @@ def generate_parameters(cls, base=None, **kwds): if params[-1] == range: n = params[-2] for c in range(-n, n + 1): - yield cls.ParameterTuple._make(params[:-1] + (c,)) + yield cls._make(params[:-1] + (c,)) else: - yield cls.ParameterTuple._make(params) + yield cls._make(params) + + def __str__(self): + return self.template.format(scalar=self.scalar, const=self.const, n=self.n, c=self.c) + + +class ArrayTestCase(unittest.TestCase, CompilationTestMixin): + + context = SnippetContext( + """ + #include "ndarray/Array.hpp" + + using namespace ndarray; + + namespace { + + template + bool accept_Array(Array const & a) { + return true; + } + + } // + """ + ) def __init__(self, method, parameters=None): super().__init__(method) self.parameters = parameters + def runConversionTest(self, valid, invalid, stderr_regex=None): + # Try compiling expected failures separately, since otherwise they'd hide each other. + for out_params in invalid: + with self.subTest(**out_params._asdict()): + self.assertDoesNotCompile( + ["Array{} a;".format(self.parameters), + "accept_Array{}(a);".format(out_params)], + stderr_regex=stderr_regex, + context=self.context + ) + # Compile expected successes together to save compile time. + lines = ["Array{} a;".format(self.parameters)] + lines.extend("accept_Array{}(a);".format(out_params) for out_params in valid) + self.assertCompiles(lines, context=self.context) + def testContiguousConversions(self): """Test that we can convert Arrays only when we do not increase abs(C), and do not change the sign of C when C > 1. @@ -88,7 +106,7 @@ def testContiguousConversions(self): valid = [] invalid = [] with self.subTest(**self.parameters._asdict()): - for out_params in self.generate_parameters(self.parameters, c=range): + for out_params in ParameterTuple.generate(self.parameters, c=range): if out_params.n == 1: if self.parameters.c == 0 and out_params.c != 0: invalid.append(out_params) @@ -101,23 +119,36 @@ def testContiguousConversions(self): invalid.append(out_params) else: valid.append(out_params) - # Try compiling expected failures separately, since otherwise they'd hide each other. - for out_params in invalid: - with self.subTest(**out_params._asdict()): - self.assertDoesNotCompile( - [self.default_construct_template.format(p=self.parameters, var="a"), - self.accept_template.format(p=out_params, var="a")], - stderr_regex="invalid contiguousness conversion", - context=self.context - ) - # Compile expected successes together to save compile time. - lines = [self.default_construct_template.format(p=self.parameters, var="a")] - lines.extend(self.accept_template.format(p=out_params, var="a") for out_params in valid) - self.assertCompiles("\n".join(lines), context=self.context) + self.runConversionTest(valid, invalid, stderr_regex="invalid contiguousness conversion") + + def testConstConversions(self): + """Test that we can convert Arrays from T to T const, but not the + reverse. + + Unlike contiguousness conversions, this should work even when trying + to match templated signatures, because Array inherits from + Array. + """ + valid = [] + invalid = [] + with self.subTest(**self.parameters._asdict()): + for out_params in ParameterTuple.generate(self.parameters, const=["const", ""]): + if self.parameters.const and not out_params.const: + invalid.append(out_params) + else: + valid.append(out_params) + # Tests with no template signature matching. + self.runConversionTest(valid, invalid) + # Test success with template signature matching. + lines = ["Array{} a;".format(self.parameters)] + lines.extend("accept_Array(a);".format(out_params) for out_params in valid) + self.assertCompiles(lines, context=self.context) + @classmethod def makeSuite(cls): suite = unittest.TestSuite() - for p in cls.generate_parameters(ref="", scalar="float", const=("const", ""), n=(1, 2), c=range): + for p in ParameterTuple.generate(scalar="float", const=("const", ""), n=(1, 2), c=range): suite.addTest(ArrayTestCase("testContiguousConversions", parameters=p)) + suite.addTest(ArrayTestCase("testConstConversions", parameters=p)) return suite From 058b1d4353e9e703e3053059e4dfb6405c52e866 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 13 Aug 2018 06:11:14 -0700 Subject: [PATCH 15/29] Switch to f-strings in compilation tests. --- tests/test_Array.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/test_Array.py b/tests/test_Array.py index da422c4e..0dde4350 100644 --- a/tests/test_Array.py +++ b/tests/test_Array.py @@ -22,8 +22,6 @@ def load_tests(loader, tests, pattern): class ParameterTuple(namedtuple("ParameterTuple", ("scalar", "const", "n", "c"))): - template = "<{scalar} {const}, {n}, {c}>" - @classmethod def generate(cls, base=None, **kwds): """Generate ParameterTuples from cartesian products of possible values. @@ -58,7 +56,7 @@ def generate(cls, base=None, **kwds): yield cls._make(params) def __str__(self): - return self.template.format(scalar=self.scalar, const=self.const, n=self.n, c=self.c) + return f"<{self.scalar} {self.const}, {self.n}, {self.c}>" class ArrayTestCase(unittest.TestCase, CompilationTestMixin): @@ -95,8 +93,8 @@ def runConversionTest(self, valid, invalid, stderr_regex=None): context=self.context ) # Compile expected successes together to save compile time. - lines = ["Array{} a;".format(self.parameters)] - lines.extend("accept_Array{}(a);".format(out_params) for out_params in valid) + lines = [f"Array{self.parameters} a;"] + lines.extend(f"accept_Array{out_params}(a);" for out_params in valid) self.assertCompiles(lines, context=self.context) def testContiguousConversions(self): @@ -140,10 +138,7 @@ def testConstConversions(self): # Tests with no template signature matching. self.runConversionTest(valid, invalid) # Test success with template signature matching. - lines = ["Array{} a;".format(self.parameters)] - lines.extend("accept_Array(a);".format(out_params) for out_params in valid) - self.assertCompiles(lines, context=self.context) - + self.assertCompiles([f"Array{self.parameters} a;", "accept_Array(a);"], context=self.context) @classmethod def makeSuite(cls): From be7ea36f03586a8757a26944f3da0aa52e9fe3e6 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sat, 18 Aug 2018 10:17:31 -0700 Subject: [PATCH 16/29] Remove superfluous ArrayImpl ctor. --- include/ndarray/detail/ArrayImpl.hpp | 9 --------- tests/ArrayImpl.cpp | 11 ----------- 2 files changed, 20 deletions(-) diff --git a/include/ndarray/detail/ArrayImpl.hpp b/include/ndarray/detail/ArrayImpl.hpp index 37909133..569d360a 100644 --- a/include/ndarray/detail/ArrayImpl.hpp +++ b/include/ndarray/detail/ArrayImpl.hpp @@ -59,15 +59,6 @@ struct ArrayImpl { layout(Layout::make(shape, strides)) {} - template - ArrayImpl( - std::shared_ptr const & data, - std::shared_ptr const> layout_ - ) noexcept : - buffer(data, reinterpret_cast(data.get())), - layout(std::move(layout_)) - {} - ArrayImpl( std::shared_ptr buffer_, std::shared_ptr const> layout_ diff --git a/tests/ArrayImpl.cpp b/tests/ArrayImpl.cpp index d61ee514..af2b657e 100644 --- a/tests/ArrayImpl.cpp +++ b/tests/ArrayImpl.cpp @@ -97,17 +97,6 @@ TEST_CASE("detail::ArrayImpl: external data, explicit strides", "[detail][ArrayI check_index_pair(rm, cm); } -TEST_CASE("detail::ArrayImpl: external data, Layout", "[detail][ArrayImpl]") { - std::array shape = {2, 3}; - auto rm_layout = detail::Layout<2>::make(shape, sizeof(CountedElement), MemoryOrder::ROW_MAJOR); - auto cm_layout = detail::Layout<2>::make(shape, sizeof(CountedElement), MemoryOrder::COL_MAJOR); - CountedElement::reset(); - std::shared_ptr data(new CountedElement[6], std::default_delete()); - detail::ArrayImpl<2> rm(data, rm_layout); - detail::ArrayImpl<2> cm(data, cm_layout); - check_index_pair(rm, cm); -} - TEST_CASE("detail::ArrayImpl: external buffer, Layout", "[detail][ArrayImpl]") { std::array shape = {2, 3}; auto rm_layout = detail::Layout<2>::make(shape, sizeof(CountedElement), MemoryOrder::ROW_MAJOR); From 706fd10e33121123410fa5e3b3a380188c1f7c04 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sat, 18 Aug 2018 10:18:08 -0700 Subject: [PATCH 17/29] Move IndexVectorTraits out of detail:: --- include/ndarray/{detail => }/IndexVectorTraits.hpp | 9 +++------ include/ndarray/detail/Layout.hpp | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) rename include/ndarray/{detail => }/IndexVectorTraits.hpp (90%) diff --git a/include/ndarray/detail/IndexVectorTraits.hpp b/include/ndarray/IndexVectorTraits.hpp similarity index 90% rename from include/ndarray/detail/IndexVectorTraits.hpp rename to include/ndarray/IndexVectorTraits.hpp index 1794867a..3af55ca2 100644 --- a/include/ndarray/detail/IndexVectorTraits.hpp +++ b/include/ndarray/IndexVectorTraits.hpp @@ -8,8 +8,8 @@ * of the source distribution, or alternately available at: * https://github.com/ndarray/ndarray */ -#ifndef NDARRAY_detail_IndexVectorTraits_hpp_INCLUDED -#define NDARRAY_detail_IndexVectorTraits_hpp_INCLUDED +#ifndef NDARRAY_IndexVectorTraits_hpp_INCLUDED +#define NDARRAY_IndexVectorTraits_hpp_INCLUDED #include #include @@ -17,7 +17,6 @@ #include "ndarray/common.hpp" namespace ndarray { -namespace detail { template struct IndexVectorTraits { @@ -89,8 +88,6 @@ struct IndexVectorTraits> { }; - -} // namespace detail } // ndarray -#endif // !NDARRAY_detail_IndexVectorTraits_hpp_INCLUDED \ No newline at end of file +#endif // !NDARRAY_IndexVectorTraits_hpp_INCLUDED \ No newline at end of file diff --git a/include/ndarray/detail/Layout.hpp b/include/ndarray/detail/Layout.hpp index 6eb27f7b..a71a261a 100644 --- a/include/ndarray/detail/Layout.hpp +++ b/include/ndarray/detail/Layout.hpp @@ -15,7 +15,7 @@ #include "ndarray/common.hpp" #include "ndarray/exceptions.hpp" -#include "ndarray/detail/IndexVectorTraits.hpp" +#include "ndarray/IndexVectorTraits.hpp" namespace ndarray { namespace detail { From 4352ceaafa2964071c5fd8e71d1578257f4e508e Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sat, 18 Aug 2018 20:13:15 -0400 Subject: [PATCH 18/29] Add Array constructors and basic accessors. --- CMakeLists.txt | 1 + include/ndarray/Array.hpp | 191 +++++++++++++++----------- include/ndarray/ArrayInterfaceN.hpp | 62 +++++++++ include/ndarray/IndexVectorTraits.hpp | 18 +++ include/ndarray/common.hpp | 5 + include/ndarray/detail/ArrayImpl.hpp | 8 +- tests/Array.cpp | 104 ++++++++++++++ 7 files changed, 301 insertions(+), 88 deletions(-) create mode 100644 include/ndarray/ArrayInterfaceN.hpp create mode 100644 tests/Array.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f54e6c8..3adbc89c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(tests_cpp tests/main.cpp tests/Layout.cpp tests/ArrayImpl.cpp + tests/Array.cpp ) target_link_libraries(tests_cpp Catch2::Catch2 ndarray) include(Catch) diff --git a/include/ndarray/Array.hpp b/include/ndarray/Array.hpp index 265ea3bb..3384eef9 100644 --- a/include/ndarray/Array.hpp +++ b/include/ndarray/Array.hpp @@ -8,10 +8,12 @@ * of the source distribution, or alternately available at: * https://github.com/ndarray/ndarray */ -#ifndef NDARRAY_ArrayBase_hpp_INCLUDED -#define NDARRAY_ArrayBase_hpp_INCLUDED +#ifndef NDARRAY_Array_hpp_INCLUDED +#define NDARRAY_Array_hpp_INCLUDED #include "ndarray/common.hpp" +#include "ndarray/IndexVectorTraits.hpp" +#include "ndarray/ArrayInterfaceN.hpp" #include "ndarray/detail/ArrayImpl.hpp" namespace ndarray { @@ -24,115 +26,105 @@ constexpr bool contiguousness_convertible(Size n, Offset in, Offset out) { (n == 1 && (in == 1 || in == -1) && (out == 1 || out == -1)); } -constexpr Offset nested_contiguousness(Size n, Offset c) { - return (c > 0 && c == n) ? c - 1 : (c < 0 && -c == n) ? c + 1 : c; -} - } // namespace detail -template class Array; -template class Deref; - -template -class Array { +template +class Array : public ArrayInterfaceN, T const, N, C> { public: - using Reference = T const &; - Array() noexcept = default; + Array(Array const &) noexcept = default; + + Array(Array &&) noexcept = default; + template - Array(Array const & other) noexcept { - static_assert(detail::contiguousness_convertible(1, D, C), "invalid contiguousness conversion"); + Array(Array const & other) noexcept : _impl(other._impl) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); } template - Array(Array && other) noexcept { - static_assert(detail::contiguousness_convertible(1, D, C), "invalid contiguousness conversion"); + Array(Array && other) noexcept : _impl(other._impl) { + static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); } - Array & operator=(Array const &) noexcept = default; - - Array & operator=(Array &&) noexcept = default; - - Deref> operator*() const noexcept; - - Reference operator[](Size n) const; - -protected: - detail::ArrayImpl<1> _impl; -}; - -template -class Array : public Array { -public: - - using Reference = T &; - - Array() noexcept = default; + template + explicit Array( + ShapeVector const & shape, + EnableIfIndexVector order=MemoryOrder::ROW_MAJOR + ) : _impl(shape, order, detail::TypeTag()) {} + + explicit Array(std::initializer_list shape, MemoryOrder order=MemoryOrder::ROW_MAJOR) : + _impl(shape, order, detail::TypeTag()) {} + + template + Array( + std::shared_ptr data, + ShapeVector const & shape, + EnableIfIndexVector order=MemoryOrder::ROW_MAJOR + ) : _impl(std::const_pointer_cast(std::move(data)), shape, order) {} + + Array(std::shared_ptr data, std::initializer_list shape, + MemoryOrder order=MemoryOrder::ROW_MAJOR) : + _impl(std::const_pointer_cast(std::move(data)), shape, order) {} + + template + Array( + EnableIfIndexVector, ShapeVector, StridesVector> data, + ShapeVector const & shape, + StridesVector const & strides + ) : _impl(std::const_pointer_cast(std::move(data)), shape, strides) { + _impl.layout->template check_contiguousness(sizeof(T)); + } - template - Array(Array const & other) noexcept { - static_assert(detail::contiguousness_convertible(1, D, C), "invalid contiguousness conversion"); + template + Array( + std::shared_ptr data, + std::initializer_list shape, + std::initializer_list strides + ) : _impl(std::const_pointer_cast(std::move(data)), shape, strides) { + _impl.layout->template check_contiguousness(sizeof(T)); } - template - Array(Array && other) noexcept { - static_assert(detail::contiguousness_convertible(1, D, C), "invalid contiguousness conversion"); + Array(std::shared_ptr buffer, std::shared_ptr const> layout) : + _impl(std::const_pointer_cast(std::move(buffer)), std::move(layout)) + { + _impl.layout->template check_contiguousness(sizeof(T)); } Array & operator=(Array const &) noexcept = default; Array & operator=(Array &&) noexcept = default; - Deref> operator*() const noexcept; - - Reference operator[](Size n) const; - -}; - + Deref> operator*() const noexcept; -template -class Array { -public: + bool empty() const { return (!_impl.layout) || _impl.layout->size() == 0u; } - using Reference = Array; + Size size() const { return _impl.layout ? _impl.layout->size() : 0u; } - Array() noexcept = default; + Offset stride() const { return _impl.layout->stride(); } - Array(Array const &) noexcept = default; + std::array shape() const { return _impl.layout->shape(); } - Array(Array &&) noexcept = default; + std::array strides() const { return _impl.layout->strides(); } - template - Array(Array const & other) noexcept { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } - - template - Array(Array && other) noexcept { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } + Size full_size() const { return _impl.layout->full_size(); } - Array & operator=(Array const &) noexcept = default; - - Array & operator=(Array &&) noexcept = default; - - Deref> operator*() const noexcept; +protected: - Reference operator[](Size n) const; + template friend class Array; + template friend class ArrayInterfaceN; -protected: - detail::ArrayImpl<1> _impl; + detail::ArrayImpl _impl; }; + template -class Array : public Array { +class Array : public ArrayInterfaceN, T, N, C>, public Array { + using Base = Array; public: - using Reference = Array; - Array() noexcept = default; Array(Array const &) noexcept = default; @@ -140,14 +132,47 @@ class Array : public Array { Array(Array &&) noexcept = default; template - Array(Array const & other) noexcept { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } + Array(Array const & other) noexcept : Base(other) {} template - Array(Array && other) noexcept { - static_assert(detail::contiguousness_convertible(N, D, C), "invalid contiguousness conversion"); - } + Array(Array && other) noexcept : Base(other) {} + + template + explicit Array( + ShapeVector const & shape, + EnableIfIndexVector order=MemoryOrder::ROW_MAJOR + ) : Base(shape, order) {} + + explicit Array(std::initializer_list shape, MemoryOrder order=MemoryOrder::ROW_MAJOR) : + Base(shape, order) {} + + template + Array( + std::shared_ptr data, + ShapeVector const & shape, + EnableIfIndexVector order=MemoryOrder::ROW_MAJOR + ) : Base(std::move(data), shape, order) {} + + Array(std::shared_ptr data, std::initializer_list shape, + MemoryOrder order=MemoryOrder::ROW_MAJOR) : + Base(std::move(data), shape, order) {} + + template + Array( + EnableIfIndexVector, ShapeVector, StridesVector> data, + ShapeVector const & shape, + StridesVector const & strides + ) : Base(std::move(data), shape, strides) {} + + template + Array( + std::shared_ptr data, + std::initializer_list shape, + std::initializer_list strides + ) : Base(std::move(data), shape, strides) {} + + Array(std::shared_ptr buffer, std::shared_ptr const> layout) : + Base(std::move(buffer), std::move(layout)) {} Array & operator=(Array const &) noexcept = default; @@ -155,10 +180,8 @@ class Array : public Array { Deref> operator*() const noexcept; - Reference operator[](Size n) const; - }; } // namespace ndarray -#endif // !NDARRAY_ArrayBase_hpp_INCLUDED +#endif // !NDARRAY_Array_hpp_INCLUDED diff --git a/include/ndarray/ArrayInterfaceN.hpp b/include/ndarray/ArrayInterfaceN.hpp new file mode 100644 index 00000000..a03ba236 --- /dev/null +++ b/include/ndarray/ArrayInterfaceN.hpp @@ -0,0 +1,62 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_ArrayInterfaceN_hpp_INCLUDED +#define NDARRAY_ArrayInterfaceN_hpp_INCLUDED + +#include "ndarray/common.hpp" +#include "ndarray/detail/ArrayImpl.hpp" + +namespace ndarray { + +namespace detail { + +constexpr Offset nested_contiguousness(Size n, Offset c) { + return (c > 0 && c == n) ? c - 1 : (c < 0 && -c == n) ? c + 1 : c; +} + +} // namespace detail + +template +class ArrayInterfaceN { +public: + + using Reference = Element &; + + Reference operator[](Size n) const; + +private: + + detail::ArrayImpl<1> const & impl() const { + return static_cast(*this)._impl; + } + +}; + + +template +class ArrayInterfaceN { +public: + + using Reference = Array; + + Reference operator[](Size n) const; + +private: + + detail::ArrayImpl const & impl() const { + return static_cast(*this)._impl; + } + +}; + +} // namespace ndarray + +#endif // !NDARRAY_ArrayInterfaceN_hpp_INCLUDED diff --git a/include/ndarray/IndexVectorTraits.hpp b/include/ndarray/IndexVectorTraits.hpp index 3af55ca2..2f977ede 100644 --- a/include/ndarray/IndexVectorTraits.hpp +++ b/include/ndarray/IndexVectorTraits.hpp @@ -13,6 +13,7 @@ #include #include +#include #include "ndarray/common.hpp" @@ -88,6 +89,23 @@ struct IndexVectorTraits> { }; + +template +struct IsIndexVector { + static constexpr bool value = IndexVectorTraits::is_specialized && IsIndexVector::value; +}; + + +template +struct IsIndexVector { + static constexpr bool value = IndexVectorTraits::is_specialized; +}; + + +template +using EnableIfIndexVector = typename std::enable_if::value, Arg>::type; + + } // ndarray #endif // !NDARRAY_IndexVectorTraits_hpp_INCLUDED \ No newline at end of file diff --git a/include/ndarray/common.hpp b/include/ndarray/common.hpp index d6e11de8..bcb2e399 100644 --- a/include/ndarray/common.hpp +++ b/include/ndarray/common.hpp @@ -24,6 +24,11 @@ enum class MemoryOrder { COL_MAJOR }; +template class Array; +template class ArrayInterfaceN; + +template class Deref; + namespace detail { template class Layout; diff --git a/include/ndarray/detail/ArrayImpl.hpp b/include/ndarray/detail/ArrayImpl.hpp index 569d360a..f4913416 100644 --- a/include/ndarray/detail/ArrayImpl.hpp +++ b/include/ndarray/detail/ArrayImpl.hpp @@ -41,21 +41,21 @@ struct ArrayImpl { template ArrayImpl( - std::shared_ptr const & data, + std::shared_ptr data, ShapeVector const & shape, MemoryOrder order ) : - buffer(data, reinterpret_cast(data.get())), + buffer(std::move(data), reinterpret_cast(data.get())), layout(Layout::make(shape, sizeof(Element), order)) {} template ArrayImpl( - std::shared_ptr const & data, + std::shared_ptr data, ShapeVector const & shape, StridesVector const & strides ) : - buffer(data, reinterpret_cast(data.get())), + buffer(std::move(data), reinterpret_cast(data.get())), layout(Layout::make(shape, strides)) {} diff --git a/tests/Array.cpp b/tests/Array.cpp new file mode 100644 index 00000000..1cd0e459 --- /dev/null +++ b/tests/Array.cpp @@ -0,0 +1,104 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#include "catch2/catch.hpp" +#include "ndarray/Array.hpp" + +using namespace ndarray; + +namespace { + +template +class TestIndexVector { +public: + + TestIndexVector(std::initializer_list data) : _data(data) {} + + std::initializer_list initializer_list() const { return _data; } + + std::vector vector() const { return std::vector(_data.begin(), _data.end()); } + + std::array array() const { + std::array r; + std::copy(_data.begin(), _data.end(), r.begin()); + return r; + } + + T const & operator[](Size n) const { return _data.begin()[n]; } + +private: + std::initializer_list _data; +}; + +template +class TestStructure { +public: + + TestStructure(std::initializer_list shape_, std::initializer_list strides_) : + shape(shape_), strides(strides_), full_size(1) + { + for (Size i = 0; i < N; ++i) { + full_size *= shape[i]; + } + } + + template + void check(Array const & array) const { + REQUIRE(array.shape() == shape.array()); + REQUIRE(array.strides() == strides.array()); + REQUIRE(array.full_size() == full_size); + REQUIRE(array.size() == shape[0]); + REQUIRE(array.stride() == strides[0]); + REQUIRE(!array.empty()); + } + + template + void runConstructionTest(MemoryOrder order) const { + SECTION("Automatic strides with allocation") { + check(Array(shape.initializer_list(), order)); + check(Array(shape.vector(), order)); + check(Array(shape.array(), order)); + } + using U = typename std::remove_const::type; + std::shared_ptr data(new U[full_size], std::default_delete()); + SECTION("Automatic strides without allocation") { + check(Array(data, shape.initializer_list(), order)); + check(Array(data, shape.vector(), order)); + check(Array(data, shape.array(), order)); + } + SECTION("Explicit strides") { + check(Array(data, shape.initializer_list(), strides.initializer_list())); + check(Array(data, shape.vector(), strides.vector())); + check(Array(data, shape.vector(), strides.array())); + check(Array(data, shape.array(), strides.vector())); + check(Array(data, shape.array(), strides.array())); + } + } + + TestIndexVector shape; + TestIndexVector strides; + Size full_size; +}; + +} // + + +TEST_CASE("Array: construction", "[Array]") { + SECTION("Row-major contiguous") { + TestStructure<3> s = {{4, 3, 2}, {24, 8, 4}}; + s.runConstructionTest(MemoryOrder::ROW_MAJOR); + s.runConstructionTest(MemoryOrder::ROW_MAJOR); + } + SECTION("Column-major contiguous") { + TestStructure<3> s = {{4, 3, 2}, {8, 32, 96}}; + s.runConstructionTest(MemoryOrder::COL_MAJOR); + s.runConstructionTest(MemoryOrder::COL_MAJOR); + } +} \ No newline at end of file From 7621d7adbdddb068ce23a88b8e84cb781449d8e9 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Sun, 19 Aug 2018 19:26:01 -0400 Subject: [PATCH 19/29] Rewrite error handling to make exceptions optional. --- include/ndarray/detail/Layout.hpp | 12 +++- include/ndarray/errors.hpp | 116 ++++++++++++++++++++++++++++++ include/ndarray/exceptions.hpp | 43 ----------- tests/Layout.cpp | 48 +++++++------ 4 files changed, 153 insertions(+), 66 deletions(-) create mode 100644 include/ndarray/errors.hpp delete mode 100644 include/ndarray/exceptions.hpp diff --git a/include/ndarray/detail/Layout.hpp b/include/ndarray/detail/Layout.hpp index a71a261a..174cea1a 100644 --- a/include/ndarray/detail/Layout.hpp +++ b/include/ndarray/detail/Layout.hpp @@ -14,7 +14,7 @@ #include #include "ndarray/common.hpp" -#include "ndarray/exceptions.hpp" +#include "ndarray/errors.hpp" #include "ndarray/IndexVectorTraits.hpp" namespace ndarray { @@ -140,7 +140,7 @@ class Layout : public Layout { return n_contiguous_dims; } - // Throw NoncontiguousError if this does not have at least C row-major + // Set Error::NONCONTIGUOUS if this does not have at least C row-major // contiguous dimensions (starting from the innermost) or, if C is negative // at least -C column-major contiguous dimensions (starting from the // outermost). @@ -150,14 +150,20 @@ class Layout : public Layout { static_cast(N) >= C && -static_cast(N) <= C, "Cannot have more contiguous dimensions than total dimensions." ); +#if NDARRAY_ASSERT_AUDIT_ENABLED if (C == 0) return; Size n_contiguous_dims = count_contiguous_dims( element_size, C > 0 ? MemoryOrder::ROW_MAJOR : MemoryOrder::COL_MAJOR ); if (std::abs(C) > n_contiguous_dims) { - throw NoncontiguousError(n_contiguous_dims, C); + NDARRAY_FAIL( + Error::NONCONTIGUOUS, + fmt::format("At least {:d} {:s} contiguous dimensions required; array has {:d}.", + std::abs(C), C > 0 ? "row-major" : "column-major", n_contiguous_dims) + ); } +#endif } protected: diff --git a/include/ndarray/errors.hpp b/include/ndarray/errors.hpp new file mode 100644 index 00000000..00d66d36 --- /dev/null +++ b/include/ndarray/errors.hpp @@ -0,0 +1,116 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_errors_hpp_INCLUDED +#define NDARRAY_errors_hpp_INCLUDED + +#include +#include +#include "fmt/format.h" +#include "ndarray/common.hpp" + +namespace ndarray { + +class Error { +public: + + enum Category { NONCONTIGUOUS }; + + typedef void (*Handler)(Category, char const * file, int line, std::string const & message); + + class ScopedHandler { + public: + + explicit ScopedHandler(Handler handler) : _old(get_handler()) { + set_handler(handler); + } + + ScopedHandler(ScopedHandler const &) = delete; + ScopedHandler(ScopedHandler &&) = delete; + + ScopedHandler & operator=(ScopedHandler const &) = delete; + ScopedHandler & operator=(ScopedHandler &&) = delete; + + ~ScopedHandler() { set_handler(_old); } + + private: + Handler _old; + }; + + template + static void throw_handler(Category category, char const * file, int line, std::string const & message) { + throw Exception(message); + } + + static void set_handler(Handler handler=nullptr) { + get_handler() = handler; + } + + [[ noreturn ]] static void invoke ( + Category category, + char const * file, + int line, + std::string const & message + ) { + Handler handler = get_handler(); + if (handler) { + handler(category, file, line, message); + } else { + fmt::print(stderr, "{:s}:{:d}: {:s}", file, line, message); + } + std::abort(); + } + +private: + + static Handler & get_handler() { + static Handler handler = nullptr; + return handler; + } +}; + + +#define NDARRAY_FAIL(CATEGORY, MESSAGE) \ + Error::invoke(CATEGORY, __FILE__, __LINE__, (MESSAGE)) + + +#ifndef NDARRAY_ASSERT_AUDIT_ENABLED + #define NDARRAY_ASSERT_AUDIT_ENABLED false +#endif + +#if NDARRAY_ASSERT_AUDIT_ENABLED + #define NDARRAY_ASSERT_CHECK_ENABLED true +#else + #ifndef NDARRAY_ASSERT_CHECK_ENABLED + #ifdef NDEBUG + #define NDARRAY_ASSERT_CHECK_ENABLED false + #else + #define NDARRAY_ASSERT_CHECK_ENABLED true + #endif + #endif +#endif + +#if NDARRAY_ASSERT_AUDIT_ENABLED + #define NDARRAY_ASSERT_AUDIT(CONDITION, CATEGORY, MESSAGE) \ + if (!(CONDITION)) NDARRAY_FAIL(CATEGORY, MESSAGE) +#else + #define NDARRAY_ASSERT_AUDIT(CONDITION, CATEGORY, MESSAGE) ((void)0) +#endif + +#if NDARRAY_ASSERT_CHECK_ENABLED + #define NDARRAY_ASSERT_CHECK(CONDITION, CATEGORY, MESSAGE) \ + if (!(CONDITION)) NDARRAY_FAIL(CATEGORY, MESSAGE) +#else + #define NDARRAY_ASSERT_CHECK(CONDITION, CATEGORY, MESSAGE) ((void)0) +#endif + +} // ndarray + +#endif // !NDARRAY_errors_hpp_INCLUDED diff --git a/include/ndarray/exceptions.hpp b/include/ndarray/exceptions.hpp deleted file mode 100644 index 2182bd2a..00000000 --- a/include/ndarray/exceptions.hpp +++ /dev/null @@ -1,43 +0,0 @@ -// -*- c++ -*- -/* - * Copyright (c) 2010-2018, Jim Bosch - * All rights reserved. - * - * ndarray is distributed under a simple BSD-like license; - * see the LICENSE file that should be present in the root - * of the source distribution, or alternately available at: - * https://github.com/ndarray/ndarray - */ -#ifndef NDARRAY_exceptions_hpp_INCLUDED -#define NDARRAY_exceptions_hpp_INCLUDED - -#include -#include "fmt/format.h" -#include "ndarray/common.hpp" - -namespace ndarray { - -class NoncontiguousError : public std::logic_error { - - static std::string format(Size actual, Offset required) { - static constexpr auto s = "Template parameters require at least {:d} {:s} " - "contiguous parameters; array only has {:d}"; - if (required > 0) { - return fmt::format(s, required, "row-major", actual); - } - return fmt::format(s, -required, "column-major", actual); - } - -public: - - explicit NoncontiguousError(char const * msg) : std::logic_error(msg) {} - - explicit NoncontiguousError(Size actual, Offset required) : - std::logic_error(format(actual, required)) - {} - -}; - -} // ndarray - -#endif // !NDARRAY_exceptions_hpp_INCLUDED diff --git a/tests/Layout.cpp b/tests/Layout.cpp index 7f4ce305..f75ceb9b 100644 --- a/tests/Layout.cpp +++ b/tests/Layout.cpp @@ -10,6 +10,9 @@ */ #include #include "catch2/catch.hpp" + +#define NDARRAY_ASSERT_AUDIT_ENABLED true + #include "ndarray/detail/Layout.hpp" using namespace ndarray; @@ -73,6 +76,7 @@ void check_layout( } // TEST_CASE("detail::Layout: automatic row-major contiguous strides", "[detail][Layout]") { + Error::ScopedHandler errors(&Error::throw_handler); Size const element_size = 2; std::array shape = {3, 4, 5}; std::array strides = {40, 10, 2}; @@ -82,35 +86,37 @@ TEST_CASE("detail::Layout: automatic row-major contiguous strides", "[detail][La REQUIRE_NOTHROW(layout->check_contiguousness<2>(element_size)); REQUIRE_NOTHROW(layout->check_contiguousness<1>(element_size)); REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); - REQUIRE_THROWS_AS(layout->check_contiguousness<-1>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<-2>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-1>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<-2>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), std::logic_error); } TEST_CASE("detail::Layout: explicit partially row-major strides", "[detail][Layout]") { + Error::ScopedHandler errors(&Error::throw_handler); Size const element_size = 2; std::array shape = {3, 4, 5}; std::array strides = {80, 10, 2}; auto layout = detail::Layout<3>::make(shape, strides); check_layout(*layout, shape, strides); - REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), std::logic_error); REQUIRE_NOTHROW(layout->check_contiguousness<2>(element_size)); REQUIRE_NOTHROW(layout->check_contiguousness<1>(element_size)); REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); - REQUIRE_THROWS_AS(layout->check_contiguousness<-1>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<-2>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-1>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<-2>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), std::logic_error); } TEST_CASE("detail::Layout: automatic column-major contiguous strides", "[detail][Layout]") { + Error::ScopedHandler errors(&Error::throw_handler); Size const element_size = 2; std::array shape = {3, 4, 5}; std::array strides = {2, 6, 24}; auto layout = detail::Layout<3>::make(shape, element_size, MemoryOrder::COL_MAJOR); check_layout(*layout, shape, strides); - REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<2>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<1>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<2>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<1>(element_size), std::logic_error); REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); REQUIRE_NOTHROW(layout->check_contiguousness<-1>(element_size)); REQUIRE_NOTHROW(layout->check_contiguousness<-2>(element_size)); @@ -118,31 +124,33 @@ TEST_CASE("detail::Layout: automatic column-major contiguous strides", "[detail] } TEST_CASE("detail::Layout: explicit partially column-major strides", "[detail][Layout]") { + Error::ScopedHandler errors(&Error::throw_handler); Size const element_size = 2; std::array shape = {3, 4, 5}; std::array strides = {2, 6, 48}; auto layout = detail::Layout<3>::make(shape, strides); check_layout(*layout, shape, strides); - REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<2>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<1>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<2>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<1>(element_size), std::logic_error); REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); REQUIRE_NOTHROW(layout->check_contiguousness<-1>(element_size)); REQUIRE_NOTHROW(layout->check_contiguousness<-2>(element_size)); - REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), std::logic_error); } TEST_CASE("detail::Layout: explicit noncontiguous strides", "[detail][Layout]") { + Error::ScopedHandler errors(&Error::throw_handler); Size const element_size = 2; std::array shape = {3, 4, 5}; std::array strides = {4, 60, 12}; auto layout = detail::Layout<3>::make(shape, strides); check_layout(*layout, shape, strides); - REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<2>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<1>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<3>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<2>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<1>(element_size), std::logic_error); REQUIRE_NOTHROW(layout->check_contiguousness<0>(element_size)); - REQUIRE_THROWS_AS(layout->check_contiguousness<-1>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<-2>(element_size), NoncontiguousError); - REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), NoncontiguousError); + REQUIRE_THROWS_AS(layout->check_contiguousness<-1>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<-2>(element_size), std::logic_error); + REQUIRE_THROWS_AS(layout->check_contiguousness<-3>(element_size), std::logic_error); } From 37d5fb5b2ed55f72757992329e7eb29673e2f768 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 20 Aug 2018 21:10:46 -0400 Subject: [PATCH 20/29] Add more array construction tests. --- tests/Array.cpp | 54 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/tests/Array.cpp b/tests/Array.cpp index 1cd0e459..d4ef53e4 100644 --- a/tests/Array.cpp +++ b/tests/Array.cpp @@ -9,6 +9,8 @@ * https://github.com/ndarray/ndarray */ #include "catch2/catch.hpp" + +#define NDARRAY_ASSERT_AUDIT_ENABLED true #include "ndarray/Array.hpp" using namespace ndarray; @@ -60,7 +62,7 @@ class TestStructure { } template - void runConstructionTest(MemoryOrder order) const { + void runContiguousConstructionTest(MemoryOrder order) const { SECTION("Automatic strides with allocation") { check(Array(shape.initializer_list(), order)); check(Array(shape.vector(), order)); @@ -82,6 +84,25 @@ class TestStructure { } } + template + void runBadStrideTest() const { + Error::ScopedHandler errors(&Error::throw_handler); + using U = typename std::remove_const::type; + std::shared_ptr data(new U[full_size], std::default_delete()); + auto construct1 = [data, this]() { + return Array(data, shape.initializer_list(), strides.initializer_list()); + }; + auto construct2 = [data, this]() { + return Array(data, shape.vector(), strides.vector()); + }; + auto construct3 = [data, this]() { + return Array(data, shape.array(), strides.array()); + }; + REQUIRE_THROWS_AS(construct1(), std::logic_error); + REQUIRE_THROWS_AS(construct2(), std::logic_error); + REQUIRE_THROWS_AS(construct3(), std::logic_error); + } + TestIndexVector shape; TestIndexVector strides; Size full_size; @@ -91,14 +112,31 @@ class TestStructure { TEST_CASE("Array: construction", "[Array]") { + TestStructure<3> rmc = {{4, 3, 2}, {24, 8, 4}}; SECTION("Row-major contiguous") { - TestStructure<3> s = {{4, 3, 2}, {24, 8, 4}}; - s.runConstructionTest(MemoryOrder::ROW_MAJOR); - s.runConstructionTest(MemoryOrder::ROW_MAJOR); + rmc.runContiguousConstructionTest(MemoryOrder::ROW_MAJOR); + rmc.runContiguousConstructionTest(MemoryOrder::ROW_MAJOR); } + TestStructure<3> cmc = {{4, 3, 2}, {8, 32, 96}}; SECTION("Column-major contiguous") { - TestStructure<3> s = {{4, 3, 2}, {8, 32, 96}}; - s.runConstructionTest(MemoryOrder::COL_MAJOR); - s.runConstructionTest(MemoryOrder::COL_MAJOR); + cmc.runContiguousConstructionTest(MemoryOrder::COL_MAJOR); + cmc.runContiguousConstructionTest(MemoryOrder::COL_MAJOR); + } + SECTION("Non-contiguous") { + // Row-major contiguous strides are not at all column-major contiguous + rmc.runBadStrideTest(); + rmc.runBadStrideTest(); + rmc.runBadStrideTest(); + // Colum-major contiguous strides are not at all row-major contiguous + cmc.runBadStrideTest(); + cmc.runBadStrideTest(); + cmc.runBadStrideTest(); + // Contiguous strides for double are not at contiguous for float + cmc.runBadStrideTest(); + cmc.runBadStrideTest(); + cmc.runBadStrideTest(); + cmc.runBadStrideTest(); + cmc.runBadStrideTest(); + cmc.runBadStrideTest(); } -} \ No newline at end of file +} From dcf8b0f1cd878924372e155ae25fc87a8a0ab1c1 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 3 Sep 2018 12:06:28 -0400 Subject: [PATCH 21/29] Add index accessors to Array. --- include/ndarray/Array.hpp | 25 ++++++++++++++++++++----- include/ndarray/ArrayInterfaceN.hpp | 21 +++++++++++++++++++-- include/ndarray/errors.hpp | 2 +- tests/Array.cpp | 24 ++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/include/ndarray/Array.hpp b/include/ndarray/Array.hpp index 3384eef9..295d2abc 100644 --- a/include/ndarray/Array.hpp +++ b/include/ndarray/Array.hpp @@ -29,10 +29,14 @@ constexpr bool contiguousness_convertible(Size n, Offset in, Offset out) { } // namespace detail -template -class Array : public ArrayInterfaceN, T const, N, C> { +template +class Array : public ArrayInterfaceN, T const, N_, C_> { public: + using Element = T const; + static constexpr Size N = N_; + static constexpr Offset C = C_; + Array() noexcept = default; Array(Array const &) noexcept = default; @@ -111,6 +115,8 @@ class Array : public ArrayInterfaceN, T cons Size full_size() const { return _impl.layout->full_size(); } + T const * data() const { return reinterpret_cast(_impl.buffer.get()); } + protected: template friend class Array; @@ -120,11 +126,16 @@ class Array : public ArrayInterfaceN, T cons }; -template -class Array : public ArrayInterfaceN, T, N, C>, public Array { - using Base = Array; +template +class Array : public ArrayInterfaceN, T, N_, C_>, public Array { + using Base = Array; + using Interface = ArrayInterfaceN, T, N_, C_>; public: + using Element = T; + static constexpr Size N = N_; + static constexpr Offset C = C_; + Array() noexcept = default; Array(Array const &) noexcept = default; @@ -180,6 +191,10 @@ class Array : public ArrayInterfaceN, T, N, C>, public Array> operator*() const noexcept; + T * data() const { return reinterpret_cast(this->_impl.buffer.get()); } + + using Interface::operator[]; + }; } // namespace ndarray diff --git a/include/ndarray/ArrayInterfaceN.hpp b/include/ndarray/ArrayInterfaceN.hpp index a03ba236..6a089314 100644 --- a/include/ndarray/ArrayInterfaceN.hpp +++ b/include/ndarray/ArrayInterfaceN.hpp @@ -12,6 +12,7 @@ #define NDARRAY_ArrayInterfaceN_hpp_INCLUDED #include "ndarray/common.hpp" +#include "ndarray/errors.hpp" #include "ndarray/detail/ArrayImpl.hpp" namespace ndarray { @@ -30,7 +31,12 @@ class ArrayInterfaceN { using Reference = Element &; - Reference operator[](Size n) const; + Reference operator[](Size n) const { + NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); + NDARRAY_ASSERT_AUDIT(impl().buffer != nullptr, Error::UNINITIALIZED, "buffer is null"); + NDARRAY_ASSERT_AUDIT(n < impl().layout->size(), Error::OUT_OF_BOUNDS, "array index out of bounds"); + return *reinterpret_cast(impl().buffer.get() + n*impl().layout->stride()); + } private: @@ -47,7 +53,18 @@ class ArrayInterfaceN { using Reference = Array; - Reference operator[](Size n) const; + Reference operator[](Size n) const { + NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); + NDARRAY_ASSERT_AUDIT(impl().buffer != nullptr, Error::UNINITIALIZED, "buffer is null"); + NDARRAY_ASSERT_AUDIT(n < impl().layout->size(), Error::OUT_OF_BOUNDS, "array index out of bounds"); + return Reference( + std::shared_ptr( + impl().buffer, + impl().buffer.get() + n*impl().layout->stride() + ), + impl().layout + ); + } private: diff --git a/include/ndarray/errors.hpp b/include/ndarray/errors.hpp index 00d66d36..b150a2b4 100644 --- a/include/ndarray/errors.hpp +++ b/include/ndarray/errors.hpp @@ -21,7 +21,7 @@ namespace ndarray { class Error { public: - enum Category { NONCONTIGUOUS }; + enum Category { NONCONTIGUOUS, UNINITIALIZED, OUT_OF_BOUNDS }; typedef void (*Handler)(Category, char const * file, int line, std::string const & message); diff --git a/tests/Array.cpp b/tests/Array.cpp index d4ef53e4..f37a6ccc 100644 --- a/tests/Array.cpp +++ b/tests/Array.cpp @@ -8,6 +8,8 @@ * of the source distribution, or alternately available at: * https://github.com/ndarray/ndarray */ +#include + #include "catch2/catch.hpp" #define NDARRAY_ASSERT_AUDIT_ENABLED true @@ -140,3 +142,25 @@ TEST_CASE("Array: construction", "[Array]") { cmc.runBadStrideTest(); } } + + +TEST_CASE("Array: indexing", "[Array]") { + Array array({4, 3, 2}); + std::iota(array.data(), array.data() + array.full_size(), 0); + int n = 0; + for (Size i = 0; i != array.size(); ++i) { + auto r1 = array[i]; + REQUIRE(Size(decltype(r1)::N) == 2u); // extra Size(...) cast to avoid taking address of constexpr + REQUIRE(Size(decltype(r1)::C) == 2u); + for (Size j = 0; j != r1.size(); ++j) { + auto r2 = r1[j]; + REQUIRE(Size(decltype(r2)::N) == 1u); // extra Size(...) cast to avoid taking address of constexpr + REQUIRE(Size(decltype(r2)::C) == 1u); + for (Size k = 0; k != r2.size(); ++k) { + int & v = r2[k]; + REQUIRE(v == n); + ++n; + } + } + } +} \ No newline at end of file From b0db2194f0347ce735846b50d04a18e2463859f6 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Mon, 3 Sep 2018 15:35:24 -0400 Subject: [PATCH 22/29] Add iterators. --- include/ndarray/Array.hpp | 31 +++-- include/ndarray/ArrayInterfaceN.hpp | 63 +++++++++- include/ndarray/NestedIterator.hpp | 163 ++++++++++++++++++++++++++ include/ndarray/StridedIterator.hpp | 171 ++++++++++++++++++++++++++++ include/ndarray/common.hpp | 3 + include/ndarray/errors.hpp | 2 +- tests/Array.cpp | 21 +++- 7 files changed, 443 insertions(+), 11 deletions(-) create mode 100644 include/ndarray/NestedIterator.hpp create mode 100644 include/ndarray/StridedIterator.hpp diff --git a/include/ndarray/Array.hpp b/include/ndarray/Array.hpp index 295d2abc..2ce1301a 100644 --- a/include/ndarray/Array.hpp +++ b/include/ndarray/Array.hpp @@ -101,11 +101,17 @@ class Array : public ArrayInterfaceN, T Array & operator=(Array &&) noexcept = default; - Deref> operator*() const noexcept; + template + bool operator==(Array const & rhs) const { return _impl == rhs._impl; } - bool empty() const { return (!_impl.layout) || _impl.layout->size() == 0u; } + template + bool operator!=(Array const & rhs) const { return !(*this == rhs); } - Size size() const { return _impl.layout ? _impl.layout->size() : 0u; } + Deref> operator*() const; + + bool empty() const noexcept { return (!_impl.layout) || _impl.layout->size() == 0u; } + + Size size() const noexcept { return _impl.layout ? _impl.layout->size() : 0u; } Offset stride() const { return _impl.layout->stride(); } @@ -115,12 +121,13 @@ class Array : public ArrayInterfaceN, T Size full_size() const { return _impl.layout->full_size(); } - T const * data() const { return reinterpret_cast(_impl.buffer.get()); } + T const * data() const noexcept { return reinterpret_cast(_impl.buffer.get()); } protected: template friend class Array; template friend class ArrayInterfaceN; + template friend class NestedIterator; detail::ArrayImpl _impl; }; @@ -189,14 +196,24 @@ class Array : public ArrayInterfaceN, T, N_, C_>, public Array< Array & operator=(Array &&) noexcept = default; - Deref> operator*() const noexcept; + void swap(Array & other) noexcept { this->_impl.swap(other._impl); } - T * data() const { return reinterpret_cast(this->_impl.buffer.get()); } + Deref> operator*() const; - using Interface::operator[]; + T * data() const noexcept { return reinterpret_cast(this->_impl.buffer.get()); } + using Interface::operator[]; + using Interface::begin; + using Interface::end; }; +template +void swap(Array & a, Array & b) { + static_assert(std::is_same::value, + "Cannot swap arrays with different types."); + a.swap(b); +} + } // namespace ndarray #endif // !NDARRAY_Array_hpp_INCLUDED diff --git a/include/ndarray/ArrayInterfaceN.hpp b/include/ndarray/ArrayInterfaceN.hpp index 6a089314..5af7aad3 100644 --- a/include/ndarray/ArrayInterfaceN.hpp +++ b/include/ndarray/ArrayInterfaceN.hpp @@ -14,6 +14,8 @@ #include "ndarray/common.hpp" #include "ndarray/errors.hpp" #include "ndarray/detail/ArrayImpl.hpp" +#include "ndarray/StridedIterator.hpp" +#include "ndarray/NestedIterator.hpp" namespace ndarray { @@ -25,15 +27,63 @@ constexpr Offset nested_contiguousness(Size n, Offset c) { } // namespace detail + +template +class ArrayInterfaceN { +public: + + using Reference = Element &; + using Iterator = Element *; + + Iterator begin() const { + return reinterpret_cast(impl().buffer.get()); + } + + Iterator end() const { + NDARRAY_ASSERT_AUDIT(impl().buffer != nullptr, Error::UNINITIALIZED, "buffer is null"); + NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); + return reinterpret_cast(impl().buffer.get() + + impl().layout->size()*impl().layout->stride()); + } + + Reference operator[](Size n) const { + NDARRAY_ASSERT_AUDIT(impl().buffer != nullptr, Error::UNINITIALIZED, "buffer is null"); + NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); + NDARRAY_ASSERT_AUDIT(n < impl().layout->size(), Error::OUT_OF_BOUNDS, "array index out of bounds"); + return *reinterpret_cast(impl().buffer.get() + n*impl().layout->stride()); + } + +private: + + detail::ArrayImpl<1> const & impl() const { + return static_cast(*this)._impl; + } + +}; + + template class ArrayInterfaceN { public: using Reference = Element &; + using Iterator = StridedIterator; - Reference operator[](Size n) const { + Iterator begin() const { NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); + return Iterator(impl().buffer.get(), impl().layout->stride()); + } + + Iterator end() const { NDARRAY_ASSERT_AUDIT(impl().buffer != nullptr, Error::UNINITIALIZED, "buffer is null"); + NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); + return Iterator(impl().buffer.get() + impl().layout->size()*impl().layout->stride(), + impl().layout->stride()); + } + + Reference operator[](Size n) const { + NDARRAY_ASSERT_AUDIT(impl().buffer != nullptr, Error::UNINITIALIZED, "buffer is null"); + NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); NDARRAY_ASSERT_AUDIT(n < impl().layout->size(), Error::OUT_OF_BOUNDS, "array index out of bounds"); return *reinterpret_cast(impl().buffer.get() + n*impl().layout->stride()); } @@ -52,11 +102,20 @@ class ArrayInterfaceN { public: using Reference = Array; + using Iterator = NestedIterator; + + Iterator begin() const { + return Iterator(Reference(impl().buffer, impl().layout)); + } + + Iterator end() const { + NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); + return Iterator((*this)[impl().layout->size()]); + } Reference operator[](Size n) const { NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); NDARRAY_ASSERT_AUDIT(impl().buffer != nullptr, Error::UNINITIALIZED, "buffer is null"); - NDARRAY_ASSERT_AUDIT(n < impl().layout->size(), Error::OUT_OF_BOUNDS, "array index out of bounds"); return Reference( std::shared_ptr( impl().buffer, diff --git a/include/ndarray/NestedIterator.hpp b/include/ndarray/NestedIterator.hpp new file mode 100644 index 00000000..e819a751 --- /dev/null +++ b/include/ndarray/NestedIterator.hpp @@ -0,0 +1,163 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_NestedIterator_hpp_INCLUDED +#define NDARRAY_NestedIterator_hpp_INCLUDED + +#include +#include + +#include "ndarray/common.hpp" +#include "ndarray/errors.hpp" + +namespace ndarray { + +template +class NestedIterator { +public: + + using value_type = Array; + using reference = value_type const &; + using pointer = value_type const *; + using difference_type = Offset; + using iterator_category = std::input_iterator_tag; // would be random-access, but not multi-pass + + NestedIterator() : _array() {} + + explicit NestedIterator(Array array) : _array(std::move(array)) {} + + NestedIterator(NestedIterator const &) noexcept = default; + NestedIterator(NestedIterator &&) noexcept = default; + + NestedIterator & operator=(NestedIterator const &) noexcept = default; + NestedIterator & operator=(NestedIterator &&) noexcept = default; + + ~NestedIterator() noexcept = default; + + void swap(NestedIterator & other) noexcept { + using namespace std; + swap(_array, other._array); + } + + friend void swap(NestedIterator & a, NestedIterator & b) noexcept { + a.swap(b); + } + + template + bool operator==(NestedIterator const & rhs) const { return _array == rhs._array; } + + template + bool operator!=(NestedIterator const & rhs) const { return !(*this == rhs); } + + template + bool operator<(NestedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(rhs._array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + return _array.data() < rhs._array.data(); + } + + template + bool operator>(NestedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(rhs._array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + return _array.data() > rhs._array.data(); + } + + template + bool operator<=(NestedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(rhs._array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + return _array.data() <= rhs._array.data(); + } + + template + bool operator>=(NestedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(rhs._array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + return _array.data() >= rhs._array.data(); + } + + reference operator*() const { return _array; } + + pointer operator->() const { return &_array; } + + NestedIterator & operator++() { return _advance(1); } + + NestedIterator & operator--() { return _advance(-1); } + + NestedIterator operator++(int) { + NestedIterator copy(*this); + ++(*this); + return copy; + } + + NestedIterator operator--(int) { + NestedIterator copy(*this); + --(*this); + return copy; + } + + NestedIterator & operator+=(difference_type n) { return _advance(n); } + + NestedIterator & operator-=(difference_type n) { return _advance(-n); } + + NestedIterator operator+(difference_type n) const { + return NestedIterator(*this) += n; + } + + NestedIterator operator-(difference_type n) const { + return NestedIterator(*this) -= n; + } + + friend NestedIterator operator+(difference_type n, NestedIterator iter) { + return iter + n; + } + + template + difference_type operator-(NestedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(rhs._array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(_stride() == rhs._stride(), Error::INCOMPATIBLE_ARGUMENTS, + "iterators have different strides"); + NDARRAY_ASSERT_CHECK(_stride() != 0, Error::UNINITIALIZED, "iterator stride is zero"); + NDARRAY_ASSERT_CHECK((_array._impl.data() - rhs._array._impl.data()) % _stride() == 0, + Error::INCOMPATIBLE_ARGUMENTS, + "iterator pointer offset is not a multiple of stride"); + return (_array._impl.data() - rhs._array._impl.data())/_stride(); + } + + value_type operator[](difference_type n) const { + return *NestedIterator(*this)._advance(n); + } + +private: + + template friend class NestedIterator; + + Offset _stride() const { + return static_cast const &>(*_array._impl.layout).stride(); + } + + NestedIterator & _advance(Offset n) { + NDARRAY_ASSERT_CHECK(_array._impl.buffer != nullptr, Error::UNINITIALIZED, + "null iterator incremented"); + _array._impl.buffer = std::shared_ptr( + _array._impl.buffer, + _array._impl.buffer.get() + n*_stride() + ); + return *this; + } + + Array _array; +}; + +} // ndarray + +#endif // !NDARRAY_NestedIterator_hpp_INCLUDED diff --git a/include/ndarray/StridedIterator.hpp b/include/ndarray/StridedIterator.hpp new file mode 100644 index 00000000..35d67e0b --- /dev/null +++ b/include/ndarray/StridedIterator.hpp @@ -0,0 +1,171 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_StridedIterator_hpp_INCLUDED +#define NDARRAY_StridedIterator_hpp_INCLUDED + +#include +#include + +#include "ndarray/common.hpp" +#include "ndarray/errors.hpp" + +namespace ndarray { + +template +class StridedIterator { +public: + + using value_type = T; + using reference = T &; + using pointer = T *; + using difference_type = Offset; + using iterator_category = std::random_access_iterator_tag; + + StridedIterator() : _ptr(nullptr), _stride(0) {} + + StridedIterator(Byte * ptr, Offset stride) : _ptr(ptr), _stride(stride) {} + + StridedIterator(StridedIterator const &) noexcept = default; + StridedIterator(StridedIterator &&) noexcept = default; + + StridedIterator & operator=(StridedIterator const &) noexcept = default; + StridedIterator & operator=(StridedIterator &&) noexcept = default; + + ~StridedIterator() noexcept = default; + + void swap(StridedIterator & other) noexcept { + using namespace std; + swap(_ptr, other._ptr); + swap(_stride, other._stride); + } + + friend void swap(StridedIterator & a, StridedIterator & b) noexcept { + a.swap(b); + } + + template + bool operator==(StridedIterator const & rhs) const { return _ptr == rhs.ptr; } + + template + bool operator!=(StridedIterator const & rhs) const { return !(*this == rhs); } + + template + bool operator<(StridedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(rhs._ptr != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + return _ptr < rhs._ptr; + } + + template + bool operator>(StridedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(rhs._ptr != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + return _ptr > rhs._ptr; + } + + template + bool operator<=(StridedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(rhs._ptr != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + return _ptr <= rhs._ptr; + } + + template + bool operator>=(StridedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + NDARRAY_ASSERT_CHECK(rhs._ptr != nullptr, Error::UNINITIALIZED, "null iterator comparison"); + return _ptr >= rhs._ptr; + } + + reference operator*() const { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator dereferenced"); + return *reinterpret_cast(_ptr); + } + + pointer operator->() const { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator dereferenced"); + return reinterpret_cast(_ptr); + } + + StridedIterator & operator++() { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator incremented"); + _ptr += _stride; + return *this; + } + + StridedIterator & operator--() { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator decremented"); + _ptr -= _stride; + return *this; + } + + StridedIterator operator++(int) { + StridedIterator copy(*this); + ++(*this); + return copy; + } + + StridedIterator operator--(int) { + StridedIterator copy(*this); + --(*this); + return copy; + } + + StridedIterator & operator+=(difference_type n) { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator advanced"); + _ptr += _stride*n; + return *this; + } + + StridedIterator & operator-=(difference_type n) { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator advanced"); + _ptr -= _stride*n; + return *this; + } + + StridedIterator operator+(difference_type n) const { + return StridedIterator(*this) += n; + } + + StridedIterator operator-(difference_type n) const { + return StridedIterator(*this) -= n; + } + + friend StridedIterator operator+(difference_type n, StridedIterator iter) { + return iter + n; + } + + template + difference_type operator-(StridedIterator const & rhs) const { + NDARRAY_ASSERT_CHECK(_stride == rhs._stride, Error::INCOMPATIBLE_ARGUMENTS, + "iterators have different strides"); + NDARRAY_ASSERT_CHECK(_stride != 0, Error::UNINITIALIZED, "iterator stride is zero"); + NDARRAY_ASSERT_CHECK((_ptr - rhs._ptr) % _stride == 0, Error::INCOMPATIBLE_ARGUMENTS, + "iterator pointer offset is not a multiple of stride"); + return (_ptr - rhs._ptr)/_stride; + } + + reference operator[](difference_type n) const { + NDARRAY_ASSERT_CHECK(_ptr != nullptr, Error::UNINITIALIZED, "null iterator dereferenced"); + return *reinterpret_cast(_ptr + n*_stride); + } + +private: + + template friend class StridedIterator; + + Byte * _ptr; + Offset _stride; +}; + +} // ndarray + +#endif // !NDARRAY_StridedIterator_hpp_INCLUDED diff --git a/include/ndarray/common.hpp b/include/ndarray/common.hpp index bcb2e399..b92e2657 100644 --- a/include/ndarray/common.hpp +++ b/include/ndarray/common.hpp @@ -27,6 +27,9 @@ enum class MemoryOrder { template class Array; template class ArrayInterfaceN; +template class StridedIterator; +template class NestedIterator; + template class Deref; namespace detail { diff --git a/include/ndarray/errors.hpp b/include/ndarray/errors.hpp index b150a2b4..9b4a7ffc 100644 --- a/include/ndarray/errors.hpp +++ b/include/ndarray/errors.hpp @@ -21,7 +21,7 @@ namespace ndarray { class Error { public: - enum Category { NONCONTIGUOUS, UNINITIALIZED, OUT_OF_BOUNDS }; + enum Category { NONCONTIGUOUS, UNINITIALIZED, OUT_OF_BOUNDS, INCOMPATIBLE_ARGUMENTS }; typedef void (*Handler)(Category, char const * file, int line, std::string const & message); diff --git a/tests/Array.cpp b/tests/Array.cpp index f37a6ccc..c67918df 100644 --- a/tests/Array.cpp +++ b/tests/Array.cpp @@ -163,4 +163,23 @@ TEST_CASE("Array: indexing", "[Array]") { } } } -} \ No newline at end of file +} + + +TEST_CASE("Array: iterators", "[Array]") { + Array array({4, 3, 2}); + std::iota(array.data(), array.data() + array.full_size(), 0); + int n = 0; + for (auto r1 : array) { + REQUIRE(Size(decltype(r1)::N) == 2u); // extra Size(...) cast to avoid taking address of constexpr + REQUIRE(Size(decltype(r1)::C) == 2u); + for (auto r2 : r1) { + REQUIRE(Size(decltype(r2)::N) == 1u); // extra Size(...) cast to avoid taking address of constexpr + REQUIRE(Size(decltype(r2)::C) == 1u); + for (int & v : r2) { + REQUIRE(v == n); + ++n; + } + } + } +} From f7bbae658fd6eb54bd455fdd12f636db3bbf66bd Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Tue, 4 Sep 2018 10:20:29 -0400 Subject: [PATCH 23/29] Require C++17 in CMake. Code that requires C++17 should still be guarded by __cplusplus preprocessor checks. Ideally we'll make C++17 conditional in the build, but I don't know how to do that yet. --- CMakeLists.txt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3adbc89c..0a3f8989 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,13 +15,7 @@ target_include_directories(ndarray ) target_compile_features(ndarray INTERFACE - cxx_auto_type - cxx_constexpr - cxx_deleted_functions - cxx_generic_lambdas - cxx_noexcept - cxx_static_assert - cxx_strong_enums + cxx_std_17 ) target_link_libraries(ndarray INTERFACE fmt::fmt) From 5d67aac8e19b4ef60f775c261305ec48bc30ebc6 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Tue, 4 Sep 2018 10:24:17 -0400 Subject: [PATCH 24/29] Add syntax classes for views. --- CMakeLists.txt | 1 + include/ndarray/views.hpp | 162 ++++++++++++++++++++++++++++++++++++++ tests/views.cpp | 23 ++++++ 3 files changed, 186 insertions(+) create mode 100644 include/ndarray/views.hpp create mode 100644 tests/views.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a3f8989..185acd51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable(tests_cpp tests/Layout.cpp tests/ArrayImpl.cpp tests/Array.cpp + tests/views.cpp ) target_link_libraries(tests_cpp Catch2::Catch2 ndarray) include(Catch) diff --git a/include/ndarray/views.hpp b/include/ndarray/views.hpp new file mode 100644 index 00000000..3e1a68b9 --- /dev/null +++ b/include/ndarray/views.hpp @@ -0,0 +1,162 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_views_hpp_INCLUDED +#define NDARRAY_views_hpp_INCLUDED + +#include "ndarray/common.hpp" + +namespace ndarray { +namespace views { + +struct Begin {}; +struct End {}; +struct All {}; + +struct NewAxis {}; + +struct NegUnit; + +struct PosUnit { + PosUnit operator+() const { return PosUnit(); } + NegUnit operator-() const; +}; + +struct NegUnit { + NegUnit operator+() const { return NegUnit(); } + PosUnit operator-() const; +}; + +inline NegUnit PosUnit::operator-() const { return NegUnit(); } +inline PosUnit NegUnit::operator-() const { return PosUnit(); } + +#if __cplusplus >= 201703L + inline Begin begin; + inline End end; + inline PosUnit unit; + inline NewAxis newaxis; + inline All all; +#endif + +namespace detail { + +struct Index { + Offset value; +}; + +template +struct Slice { + Start start; + Stop stop; + Step step; +}; + +inline Index interpret(Offset index) { return Index{index}; } +inline NewAxis interpret(NewAxis) { return NewAxis(); } +inline Slice interpret(All) { return Slice{Begin(), End(), PosUnit()}; } + +inline Slice interpret(Offset a, Offset b, Offset c) { return Slice{a, b, c}; } +inline Slice interpret(Offset a, End b, Offset c) { return Slice{a, b, c}; } +inline Slice< Begin, Offset, Offset> interpret( Begin a, Offset b, Offset c) { return Slice< Begin, Offset, Offset>{a, b, c}; } +inline Slice< Begin, End, Offset> interpret( Begin a, End b, Offset c) { return Slice< Begin, End, Offset>{a, b, c}; } + +inline Slice interpret(Offset a, Offset b, PosUnit c=PosUnit()) { return Slice{a, b, c}; } +inline Slice interpret(Offset a, End b, PosUnit c=PosUnit()) { return Slice{a, b, c}; } +inline Slice< Begin, Offset, PosUnit> interpret( Begin a, Offset b, PosUnit c=PosUnit()) { return Slice< Begin, Offset, PosUnit>{a, b, c}; } +inline Slice< Begin, End, PosUnit> interpret( Begin a, End b, PosUnit c=PosUnit()) { return Slice< Begin, End, PosUnit>{a, b, c}; } + +inline Slice interpret(Offset a, Offset b, NegUnit c) { return Slice{a, b, c}; } +inline Slice interpret(Offset a, End b, NegUnit c) { return Slice{a, b, c}; } +inline Slice< Begin, Offset, NegUnit> interpret( Begin a, Offset b, NegUnit c) { return Slice< Begin, Offset, NegUnit>{a, b, c}; } +inline Slice< Begin, End, NegUnit> interpret( Begin a, End b, NegUnit c) { return Slice< Begin, End, NegUnit>{a, b, c}; } + +template struct Sequence; + +template +class Sequence { +public: + + template + auto operator()(Index index) const { + return append(interpret(index)); + } + + template + auto operator()(Start start, Stop stop) const { + return append(interpret(start, stop)); + } + + template + auto operator()(Start start, Stop stop, Step step) const { + return append(interpret(start, stop, step)); + } + + template + Sequence append(Next const & next) const { + return Sequence{next, *this}; + } + + Current current; +}; + +template +class Sequence { +public: + + template + auto operator()(Index index) const { + return append(interpret(index)); + } + + template + auto operator()(Start start, Stop stop) const { + return append(interpret(start, stop)); + } + + template + auto operator()(Start start, Stop stop, Step step) const { + return append(interpret(start, stop, step)); + } + + template + Sequence append(Next const & next) const { + return Sequence{next, *this}; + } + + Current current; + Sequence previous; +}; + +template +Sequence make_sequence(Next const & next) { + return Sequence{next}; +} + +} // namespace detail + +template +inline auto view(Index index) { + return detail::make_sequence(detail::interpret(index)); +} + +template +inline auto view(Start start, Stop stop) { + return detail::make_sequence(detail::interpret(start, stop)); +} + +template +inline auto view(Start start, Stop stop, Step step) { + return detail::make_sequence(detail::interpret(start, stop, step)); +} + +} // namespace views +} // ndarray + +#endif // !NDARRAY_views_hpp_INCLUDED diff --git a/tests/views.cpp b/tests/views.cpp new file mode 100644 index 00000000..bfc155dd --- /dev/null +++ b/tests/views.cpp @@ -0,0 +1,23 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#include +#include "catch2/catch.hpp" + +#define NDARRAY_ASSERT_AUDIT_ENABLED true + +#include "ndarray/views.hpp" + +using namespace ndarray::views; + + +TEST_CASE("views", "[views]") { + auto v = view(all)(1, 2)(begin, 5)(3, end)(1, 2, -unit)(newaxis)(3, 4, 2); +} From 6c0edf8ab6362bfcce977c36f72dfc5e8690a673 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Wed, 5 Sep 2018 21:06:33 -0400 Subject: [PATCH 25/29] Add string formatting to error handling. --- include/ndarray/ArrayInterfaceN.hpp | 6 ++++-- include/ndarray/IndexVectorTraits.hpp | 9 +++++++-- include/ndarray/NestedIterator.hpp | 5 +++-- include/ndarray/StridedIterator.hpp | 8 +++++--- include/ndarray/detail/Layout.hpp | 4 ++-- include/ndarray/errors.hpp | 28 +++++++++++++++++++-------- 6 files changed, 41 insertions(+), 19 deletions(-) diff --git a/include/ndarray/ArrayInterfaceN.hpp b/include/ndarray/ArrayInterfaceN.hpp index 5af7aad3..4d167cf1 100644 --- a/include/ndarray/ArrayInterfaceN.hpp +++ b/include/ndarray/ArrayInterfaceN.hpp @@ -49,7 +49,8 @@ class ArrayInterfaceN { Reference operator[](Size n) const { NDARRAY_ASSERT_AUDIT(impl().buffer != nullptr, Error::UNINITIALIZED, "buffer is null"); NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); - NDARRAY_ASSERT_AUDIT(n < impl().layout->size(), Error::OUT_OF_BOUNDS, "array index out of bounds"); + NDARRAY_ASSERT_AUDIT(n < impl().layout->size(), Error::OUT_OF_BOUNDS, + "array index {:d} out of bounds {:d}", n, impl().layout->size()); return *reinterpret_cast(impl().buffer.get() + n*impl().layout->stride()); } @@ -84,7 +85,8 @@ class ArrayInterfaceN { Reference operator[](Size n) const { NDARRAY_ASSERT_AUDIT(impl().buffer != nullptr, Error::UNINITIALIZED, "buffer is null"); NDARRAY_ASSERT_AUDIT(impl().layout != nullptr, Error::UNINITIALIZED, "layout is null"); - NDARRAY_ASSERT_AUDIT(n < impl().layout->size(), Error::OUT_OF_BOUNDS, "array index out of bounds"); + NDARRAY_ASSERT_AUDIT(n < impl().layout->size(), Error::OUT_OF_BOUNDS, + "array index {:d} out of bounds {:d}", n, impl().layout->size()); return *reinterpret_cast(impl().buffer.get() + n*impl().layout->stride()); } diff --git a/include/ndarray/IndexVectorTraits.hpp b/include/ndarray/IndexVectorTraits.hpp index 2f977ede..b78c59d3 100644 --- a/include/ndarray/IndexVectorTraits.hpp +++ b/include/ndarray/IndexVectorTraits.hpp @@ -16,6 +16,7 @@ #include #include "ndarray/common.hpp" +#include "ndarray/errors.hpp" namespace ndarray { @@ -56,7 +57,9 @@ struct IndexVectorTraits> { template static void check_dims(std::initializer_list const & v) { - assert(M == v.size()); + NDARRAY_ASSERT_CHECK(M == v.size(), + "Shape vector size ({:d}) does not match number of dimensions ({:d}).", + v.size(), M); } static Size get_size(std::initializer_list const & v, Size n) { @@ -76,7 +79,9 @@ struct IndexVectorTraits> { template static void check_dims(std::vector const & v) { - assert(M == v.size()); + NDARRAY_ASSERT_CHECK(M == v.size(), + "Shape vector size ({:d}) does not match number of dimensions ({:d}).", + v.size(), M); } static Size get_size(std::vector const & v, Size n) { diff --git a/include/ndarray/NestedIterator.hpp b/include/ndarray/NestedIterator.hpp index e819a751..94487230 100644 --- a/include/ndarray/NestedIterator.hpp +++ b/include/ndarray/NestedIterator.hpp @@ -125,11 +125,12 @@ class NestedIterator { NDARRAY_ASSERT_CHECK(_array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); NDARRAY_ASSERT_CHECK(rhs._array.data() != nullptr, Error::UNINITIALIZED, "null iterator comparison"); NDARRAY_ASSERT_CHECK(_stride() == rhs._stride(), Error::INCOMPATIBLE_ARGUMENTS, - "iterators have different strides"); + "iterators have different strides: {:d} != {:d}", _stride(), rhs._stride()); NDARRAY_ASSERT_CHECK(_stride() != 0, Error::UNINITIALIZED, "iterator stride is zero"); NDARRAY_ASSERT_CHECK((_array._impl.data() - rhs._array._impl.data()) % _stride() == 0, Error::INCOMPATIBLE_ARGUMENTS, - "iterator pointer offset is not a multiple of stride"); + "iterator pointer offset ({:d}) is not a multiple of stride ({:d})", + (_array._impl.data() - rhs._array._impl.data()), _stride()); return (_array._impl.data() - rhs._array._impl.data())/_stride(); } diff --git a/include/ndarray/StridedIterator.hpp b/include/ndarray/StridedIterator.hpp index 35d67e0b..2a946e4f 100644 --- a/include/ndarray/StridedIterator.hpp +++ b/include/ndarray/StridedIterator.hpp @@ -146,10 +146,12 @@ class StridedIterator { template difference_type operator-(StridedIterator const & rhs) const { NDARRAY_ASSERT_CHECK(_stride == rhs._stride, Error::INCOMPATIBLE_ARGUMENTS, - "iterators have different strides"); + "iterators have different strides: {:d} != {:d}", _stride, rhs._stride); NDARRAY_ASSERT_CHECK(_stride != 0, Error::UNINITIALIZED, "iterator stride is zero"); - NDARRAY_ASSERT_CHECK((_ptr - rhs._ptr) % _stride == 0, Error::INCOMPATIBLE_ARGUMENTS, - "iterator pointer offset is not a multiple of stride"); + NDARRAY_ASSERT_CHECK((_ptr - rhs._ptr) % _stride == 0, + Error::INCOMPATIBLE_ARGUMENTS, + "iterator pointer offset ({:d}) is not a multiple of stride ({:d})", + (_ptr - rhs._ptr), _stride); return (_ptr - rhs._ptr)/_stride; } diff --git a/include/ndarray/detail/Layout.hpp b/include/ndarray/detail/Layout.hpp index 174cea1a..c5ff0185 100644 --- a/include/ndarray/detail/Layout.hpp +++ b/include/ndarray/detail/Layout.hpp @@ -159,8 +159,8 @@ class Layout : public Layout { if (std::abs(C) > n_contiguous_dims) { NDARRAY_FAIL( Error::NONCONTIGUOUS, - fmt::format("At least {:d} {:s} contiguous dimensions required; array has {:d}.", - std::abs(C), C > 0 ? "row-major" : "column-major", n_contiguous_dims) + "At least {:d} {:s} contiguous dimensions required; array has {:d}.", + std::abs(C), C > 0 ? "row-major" : "column-major", n_contiguous_dims ); } #endif diff --git a/include/ndarray/errors.hpp b/include/ndarray/errors.hpp index 9b4a7ffc..a14e3a49 100644 --- a/include/ndarray/errors.hpp +++ b/include/ndarray/errors.hpp @@ -16,6 +16,7 @@ #include "fmt/format.h" #include "ndarray/common.hpp" + namespace ndarray { class Error { @@ -68,6 +69,17 @@ class Error { std::abort(); } + template + [[ noreturn ]] static void invoke ( + Category category, + char const * file, + int line, + char const * tmpl, + Args && ...args + ) { + invoke(category, file, line, fmt::format(tmpl, std::forward(args)...)); + } + private: static Handler & get_handler() { @@ -77,8 +89,8 @@ class Error { }; -#define NDARRAY_FAIL(CATEGORY, MESSAGE) \ - Error::invoke(CATEGORY, __FILE__, __LINE__, (MESSAGE)) +#define NDARRAY_FAIL(CATEGORY, ...) \ + Error::invoke(CATEGORY, __FILE__, __LINE__, __VA_ARGS__) #ifndef NDARRAY_ASSERT_AUDIT_ENABLED @@ -98,17 +110,17 @@ class Error { #endif #if NDARRAY_ASSERT_AUDIT_ENABLED - #define NDARRAY_ASSERT_AUDIT(CONDITION, CATEGORY, MESSAGE) \ - if (!(CONDITION)) NDARRAY_FAIL(CATEGORY, MESSAGE) + #define NDARRAY_ASSERT_AUDIT(CONDITION, CATEGORY, ...) \ + if (!(CONDITION)) NDARRAY_FAIL(CATEGORY, __VA_ARGS__) #else - #define NDARRAY_ASSERT_AUDIT(CONDITION, CATEGORY, MESSAGE) ((void)0) + #define NDARRAY_ASSERT_AUDIT(CONDITION, CATEGORY, ...) ((void)0) #endif #if NDARRAY_ASSERT_CHECK_ENABLED - #define NDARRAY_ASSERT_CHECK(CONDITION, CATEGORY, MESSAGE) \ - if (!(CONDITION)) NDARRAY_FAIL(CATEGORY, MESSAGE) + #define NDARRAY_ASSERT_CHECK(CONDITION, CATEGORY, ...) \ + if (!(CONDITION)) NDARRAY_FAIL(CATEGORY, __VA_ARGS__) #else - #define NDARRAY_ASSERT_CHECK(CONDITION, CATEGORY, MESSAGE) ((void)0) + #define NDARRAY_ASSERT_CHECK(CONDITION, CATEGORY, ...) ((void)0) #endif } // ndarray From 755d0a874e3e4d5b4d84678d83ed01c54c9bba56 Mon Sep 17 00:00:00 2001 From: Jim Bosch Date: Wed, 5 Sep 2018 22:34:42 -0400 Subject: [PATCH 26/29] Rough draft of Expression concept and Strided implementation. --- CMakeLists.txt | 1 + include/ndarray/expressions/Expression.hpp | 98 +++++++++++++++++ include/ndarray/expressions/Strided.hpp | 116 +++++++++++++++++++++ tests/expressions.cpp | 42 ++++++++ 4 files changed, 257 insertions(+) create mode 100644 include/ndarray/expressions/Expression.hpp create mode 100644 include/ndarray/expressions/Strided.hpp create mode 100644 tests/expressions.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 185acd51..0258662f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable(tests_cpp tests/ArrayImpl.cpp tests/Array.cpp tests/views.cpp + tests/expressions.cpp ) target_link_libraries(tests_cpp Catch2::Catch2 ndarray) include(Catch) diff --git a/include/ndarray/expressions/Expression.hpp b/include/ndarray/expressions/Expression.hpp new file mode 100644 index 00000000..0b6ec60f --- /dev/null +++ b/include/ndarray/expressions/Expression.hpp @@ -0,0 +1,98 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_EXPRESSIONS_Expression_hpp_INCLUDED +#define NDARRAY_EXPRESSIONS_Expression_hpp_INCLUDED + +#include +#include + +#include "ndarray/common.hpp" +#include "ndarray/errors.hpp" + +namespace ndarray { +namespace expressions { + + +template +struct DimensionIndex : public std::integral_constant {}; + + +template +class Expression { +public: + + static constexpr Size ndim = N; + + template + Size shape_at(DimensionIndex j) const { + return static_cast(*this).shape_at(j); + } + + template + decltype(auto) broadcast(std::array const & shape) && { + return static_cast(std::move(*this)).broadcast(shape); + } + + decltype(auto) traverse() const { + return static_cast(*this).traverse(); + } + + std::array full_shape() const { + std::array const result = {0}; + _set_shape(result, DimensionIndex<0>{}); + return result; + } + +private: + + void _set_shape(std::array & out, DimensionIndex) const {} + + template + void _set_shape(std::array & out, DimensionIndex j) const { + out[J] = shape_at(j); + NDARRAY_ASSERT_CHECK(out[J] != 0, Error::INCOMPATIBLE_ARGUMENTS, + "Expression does not define a size for index {:d}.", J); + } + +}; + + +#ifdef NDARRAY_DOCUMENTATION_ONLY + +class Traversal { +public: + + static constexpr bool is_leaf = /* unspecified */; + + using Result = /* unspecified */; + + void increment(); + + Result evaluate(); + +}; + +#endif // NDARRAY_DOCUMENTATION_ONLY + + +template +struct GetTraversal { + using Type = decltype(std::declval().traversal()); +}; + +template +using GetTraversalType = typename GetTraversal::Type; + + +} // namespace expressions +} // namespace ndarray + +#endif // !NDARRAY_EXPRESSIONS_Expression_hpp_INCLUDED diff --git a/include/ndarray/expressions/Strided.hpp b/include/ndarray/expressions/Strided.hpp new file mode 100644 index 00000000..1723e2d0 --- /dev/null +++ b/include/ndarray/expressions/Strided.hpp @@ -0,0 +1,116 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#ifndef NDARRAY_EXPRESSIONS_Strided_hpp_INCLUDED +#define NDARRAY_EXPRESSIONS_Strided_hpp_INCLUDED + +#include +#include + +#include "ndarray/expressions/Expression.hpp" + +namespace ndarray { +namespace expressions { + + +template +class StridedExpression : public Expression> { +public: + + template class Traversal; + + StridedExpression( + T * pointer, + std::array const & shape, + std::array const & strides + ) : _pointer(pointer), + _shape(shape), + _strides(strides) + {} + + template + Size shape_at(DimensionIndex j) const { return _shape[J]; } + + template + decltype(auto) broadcast(std::array const & shape) && { + static_assert(M >= N, "Cannot decrease dimensionality in broadcast."); + std::array new_strides = {0}; + for (Size j = 0u; j < N; ++j) { + if (_shape[j] != shape[j]) { + if (_shape[j] == 1u) { + new_strides[j] = 0; + } else { + NDARRAY_FAIL(Error::INCOMPATIBLE_ARGUMENTS, + "Shapes {:d} and {:d} in dimension {:d} cannot be broadcast together.", + _shape[j], shape[j], j); + } + } + } + return StridedExpression(_pointer, shape, new_strides); + } + + decltype(auto) traverse() const { + return Traversal<0>(_pointer, _strides.data()); + } + +private: + T * _pointer; + std::array _shape; + std::array _strides; +}; + + +template +template +class StridedExpression::Traversal { +public: + + static constexpr bool is_leaf = true; + + using Result = T &; + + Traversal(T * pointer, Offset const * stride) : _pointer(pointer), _stride(stride) {} + + void increment() { _pointer += *_stride; } + + Result evaluate() const { return *_pointer; } + +private: + T * _pointer; + Offset const * _stride; +}; + + +template +template +class StridedExpression::Traversal { +public: + + static constexpr bool is_leaf = false; + + using Result = Traversal; + + Traversal(T * pointer, Offset const * stride) : _pointer(pointer), _stride(stride) {} + + void increment() { _pointer += *_stride; } + + Result evaluate() const { return Result(_pointer, _stride + 1); } + +private: + T * _pointer; + Offset const * _stride; +}; + + + +} // namespace expressions +} // namespace ndarray + +#endif // !NDARRAY_EXPRESSIONS_Strided_hpp_INCLUDED diff --git a/tests/expressions.cpp b/tests/expressions.cpp new file mode 100644 index 00000000..b85b88d6 --- /dev/null +++ b/tests/expressions.cpp @@ -0,0 +1,42 @@ +// -*- c++ -*- +/* + * Copyright (c) 2010-2018, Jim Bosch + * All rights reserved. + * + * ndarray is distributed under a simple BSD-like license; + * see the LICENSE file that should be present in the root + * of the source distribution, or alternately available at: + * https://github.com/ndarray/ndarray + */ +#include +#include "catch2/catch.hpp" + +#define NDARRAY_ASSERT_AUDIT_ENABLED true + +#include "ndarray/expressions/Strided.hpp" + +using namespace ndarray; +using namespace ndarray::expressions; + +TEST_CASE("expressions", "[expressions]") { + std::array values = {0}; + std::iota(values.begin(), values.end(), 1); + std::array shape = {2, 3, 4}; + std::array strides = {12, 4, 1}; + int n = 1; + StridedExpression expr(values.data(), shape, strides); + auto t0 = expr.traverse(); + for (Size k0 = 0; k0 < shape[0]; ++k0) { + auto t1 = t0.evaluate(); + for (Size k1 = 0; k1 < shape[1]; ++k1) { + auto t2 = t1.evaluate(); + for (Size k2 = 0; k2 Date: Sun, 9 Sep 2018 17:47:09 -0400 Subject: [PATCH 27/29] Add Doxygen build. --- CMakeLists.txt | 17 + doc/doxygen.conf.in | 2482 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2499 insertions(+) create mode 100644 doc/doxygen.conf.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 0258662f..c44521f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,26 @@ project(ndarray VERSION 2.0.0 LANGUAGES CXX) set(CMAKE_EXPORT_COMPILE_COMMANDS 1) # used to obtain build commands for expected compile failure tests +option(BUILD_DOC "Build documentation" ON) + find_package(fmt 4.1 REQUIRED) find_package(Catch2 2.2.3 REQUIRED) find_package(pybind11 2.2) +find_package(Doxygen 1.8) + +if(DOXYGEN_FOUND) + set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/doc/doxygen.conf.in) + set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/doxygen.conf) + configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) + add_custom_target(doc ALL + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM + ) +else(DOXYGEN_FOUND) + message("Documentation cannot be built without Doxygen") +endif(DOXYGEN_FOUND) add_library(ndarray INTERFACE) target_include_directories(ndarray diff --git a/doc/doxygen.conf.in b/doc/doxygen.conf.in new file mode 100644 index 00000000..4ff37fa9 --- /dev/null +++ b/doc/doxygen.conf.in @@ -0,0 +1,2482 @@ +# Doxyfile 1.8.14 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "ndarray" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = 2.x + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "NumPy-like strided arrays in C++" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@/doc/ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @CMAKE_CURRENT_SOURCE_DIR@/include/ @CMAKE_CURRENT_SOURCE_DIR@/../doc + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = detail + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via Javascript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have Javascript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/ + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /