From 1e25e2a65457e8fc085a29e85a2c7ceaf3934f82 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 17 Jun 2020 16:55:46 +0200 Subject: [PATCH 01/35] Add more tests --- tests/test_report_reader.cpp | 61 ++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 45da0041..67f4d563 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -4,6 +4,16 @@ using namespace bbp::sonata; +bool testTimes(std::vector vec, double start, double step, int size) { + REQUIRE(size == vec.size()); + for (int i = 0; i < vec.size(); ++i) { + if (std::abs(vec[i] - (start + step * i) > std::numeric_limits::epsilon())) { + return false; + } + } + return true; +} + TEST_CASE("SpikeReader", "[base]") { const SpikeReader reader("./data/spikes.h5"); @@ -23,23 +33,56 @@ TEST_CASE("SpikeReader", "[base]") { SpikeReader::Population::Sorting::none); } +TEST_CASE("ReportReader limits", "[base]") { + const SomaReportReader reader("./data/somas.h5"); + + REQUIRE(reader.getPopulationsNames() == std::vector{"All", "soma1", "soma2"}); + + auto pop = reader.openPopulation("All"); + + // ids out of range + REQUIRE(pop.get(Selection({{100, 101}})).ids == DataFrame::DataType()); + REQUIRE(testTimes(pop.get(Selection({{100, 101}})).times, 0.0, 0.1, 10) == true); + REQUIRE(pop.get(Selection({{100, 101}})).data == + std::vector>({{}, {}, {}, {}, {}, {}, {}, {}, {}, {}})); + + // Inverted id + REQUIRE_THROWS(pop.get(Selection({{2, 1}}))); + + // Negative ids + REQUIRE_THROWS(pop.get(Selection({{-1, 1}}))); + + // Times out of range + REQUIRE(pop.get(Selection({{1, 2}}), 100., 101.).ids == DataFrame::DataType()); + REQUIRE(testTimes(pop.get(Selection({{1, 2}}), 100., 101.).times, 0.0, 0.0, 0) == true); + REQUIRE(pop.get(Selection({{1, 2}}), 100., 101.).data == std::vector>({})); + + // Negatives times + REQUIRE(pop.get(Selection({{1, 2}}), -1., -2.).ids == DataFrame::DataType({1})); + REQUIRE(testTimes(pop.get(Selection({{1, 2}}), -1., -2.).times, 0.0, 0.1, 10) == true); + REQUIRE(pop.get(Selection({{1, 2}}), -1., -2.).data == + std::vector>( + {{1.0}, {1.1}, {1.2}, {1.3}, {1.4}, {1.5}, {1.6}, {1.7}, {1.8}, {1.9}})); +} + TEST_CASE("ReportReader", "[base]") { const SomaReportReader reader("./data/somas.h5"); REQUIRE(reader.getPopulationsNames() == std::vector{"All", "soma1", "soma2"}); - REQUIRE(reader.openPopulation("All").getTimes() == std::make_tuple(0., 1., 0.1)); + auto pop = reader.openPopulation("All"); - REQUIRE(reader.openPopulation("All").getTimeUnits() == "ms"); + REQUIRE(pop.getTimes() == std::make_tuple(0., 1., 0.1)); - REQUIRE(reader.openPopulation("All").getDataUnits() == "mV"); + REQUIRE(pop.getTimeUnits() == "ms"); - REQUIRE(reader.openPopulation("All").getSorted()); + REQUIRE(pop.getDataUnits() == "mV"); - REQUIRE(reader.openPopulation("All").get(Selection({{3, 5}}), 0.2, 0.5).ids == - DataFrame::DataType{{3, 4}}); + REQUIRE(pop.getSorted()); - REQUIRE( - reader.openPopulation("All").get(Selection({{3, 5}}), 0.2, 0.5).data == - std::vector>{{{3.2f, 4.2f}, {3.3f, 4.3f}, {3.4f, 4.4f}, {3.5f, 4.5f}}}); + auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); + REQUIRE(data.ids == DataFrame::DataType{{3, 4}}); + REQUIRE(testTimes(data.times, 0.2, 0.1, 4) == true); + REQUIRE(data.data == std::vector>{ + {{3.2f, 4.2f}, {3.3f, 4.3f}, {3.4f, 4.4f}, {3.5f, 4.5f}}}); } From eb2a1f47174e014fe3bc3158e02b858f6002907d Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 17 Jun 2020 17:20:21 +0200 Subject: [PATCH 02/35] Add inverted times --- tests/test_report_reader.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 67f4d563..6a0b5464 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -4,14 +4,11 @@ using namespace bbp::sonata; -bool testTimes(std::vector vec, double start, double step, int size) { +void testTimes(std::vector vec, double start, double step, int size) { REQUIRE(size == vec.size()); for (int i = 0; i < vec.size(); ++i) { - if (std::abs(vec[i] - (start + step * i) > std::numeric_limits::epsilon())) { - return false; - } + REQUIRE(std::abs(vec[i] - (start + step * i)) < std::numeric_limits::epsilon()); } - return true; } TEST_CASE("SpikeReader", "[base]") { @@ -42,7 +39,7 @@ TEST_CASE("ReportReader limits", "[base]") { // ids out of range REQUIRE(pop.get(Selection({{100, 101}})).ids == DataFrame::DataType()); - REQUIRE(testTimes(pop.get(Selection({{100, 101}})).times, 0.0, 0.1, 10) == true); + testTimes(pop.get(Selection({{100, 101}})).times, 0.0, 0.1, 10); REQUIRE(pop.get(Selection({{100, 101}})).data == std::vector>({{}, {}, {}, {}, {}, {}, {}, {}, {}, {}})); @@ -54,12 +51,18 @@ TEST_CASE("ReportReader limits", "[base]") { // Times out of range REQUIRE(pop.get(Selection({{1, 2}}), 100., 101.).ids == DataFrame::DataType()); - REQUIRE(testTimes(pop.get(Selection({{1, 2}}), 100., 101.).times, 0.0, 0.0, 0) == true); + testTimes(pop.get(Selection({{1, 2}}), 100., 101.).times, 0.0, 0.0, 0); REQUIRE(pop.get(Selection({{1, 2}}), 100., 101.).data == std::vector>({})); + // Inverted times + REQUIRE(pop.get(Selection({{1, 2}}), 0.2, 0.1).ids == DataFrame::DataType({1})); + testTimes(pop.get(Selection({{1, 2}}), 0.2, 0.1).times, 0.2, 0.1, 1); + REQUIRE(pop.get(Selection({{1, 2}}), 0.2, 0.1).data == + std::vector>({{1.2}})); + // Negatives times REQUIRE(pop.get(Selection({{1, 2}}), -1., -2.).ids == DataFrame::DataType({1})); - REQUIRE(testTimes(pop.get(Selection({{1, 2}}), -1., -2.).times, 0.0, 0.1, 10) == true); + testTimes(pop.get(Selection({{1, 2}}), -1., -2.).times, 0.0, 0.1, 10); REQUIRE(pop.get(Selection({{1, 2}}), -1., -2.).data == std::vector>( {{1.0}, {1.1}, {1.2}, {1.3}, {1.4}, {1.5}, {1.6}, {1.7}, {1.8}, {1.9}})); @@ -82,7 +85,7 @@ TEST_CASE("ReportReader", "[base]") { auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); REQUIRE(data.ids == DataFrame::DataType{{3, 4}}); - REQUIRE(testTimes(data.times, 0.2, 0.1, 4) == true); + testTimes(data.times, 0.2, 0.1, 4); REQUIRE(data.data == std::vector>{ {{3.2f, 4.2f}, {3.3f, 4.3f}, {3.4f, 4.4f}, {3.5f, 4.5f}}}); } From b803812a5e527d3104011edafbd72ca7f623a195 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 17 Jun 2020 18:20:12 +0200 Subject: [PATCH 03/35] First tests for Element report --- tests/test_report_reader.cpp | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 6a0b5464..a4d2f258 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -4,7 +4,7 @@ using namespace bbp::sonata; -void testTimes(std::vector vec, double start, double step, int size) { +void testTimes(const std::vector& vec, double start, double step, int size) { REQUIRE(size == vec.size()); for (int i = 0; i < vec.size(); ++i) { REQUIRE(std::abs(vec[i] - (start + step * i)) < std::numeric_limits::epsilon()); @@ -30,7 +30,7 @@ TEST_CASE("SpikeReader", "[base]") { SpikeReader::Population::Sorting::none); } -TEST_CASE("ReportReader limits", "[base]") { +TEST_CASE("SomaReportReader limits", "[base]") { const SomaReportReader reader("./data/somas.h5"); REQUIRE(reader.getPopulationsNames() == std::vector{"All", "soma1", "soma2"}); @@ -68,7 +68,7 @@ TEST_CASE("ReportReader limits", "[base]") { {{1.0}, {1.1}, {1.2}, {1.3}, {1.4}, {1.5}, {1.6}, {1.7}, {1.8}, {1.9}})); } -TEST_CASE("ReportReader", "[base]") { +TEST_CASE("SomaReportReader", "[base]") { const SomaReportReader reader("./data/somas.h5"); REQUIRE(reader.getPopulationsNames() == std::vector{"All", "soma1", "soma2"}); @@ -89,3 +89,30 @@ TEST_CASE("ReportReader", "[base]") { REQUIRE(data.data == std::vector>{ {{3.2f, 4.2f}, {3.3f, 4.3f}, {3.4f, 4.4f}, {3.5f, 4.5f}}}); } + +TEST_CASE("ElementReportReader", "[base]") { + const ElementReportReader reader("./data/elements.h5"); + + REQUIRE(reader.getPopulationsNames() == + std::vector{"All", "element1", "element42"}); + + auto pop = reader.openPopulation("All"); + + REQUIRE(pop.getTimes() == std::make_tuple(0., 4., 0.2)); + + REQUIRE(pop.getTimeUnits() == "ms"); + + REQUIRE(pop.getDataUnits() == "mV"); + + REQUIRE(pop.getSorted()); + + auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); + REQUIRE(data.ids == + DataFrame>::DataType{ + {{3, 5}, {3, 5}, {3, 6}, {3, 6}, {3, 7}, {4, 7}, {4, 8}, {4, 8}, {4, 9}, {4, 9}}}); + testTimes(data.times, 0.2, 0.2, 2); + REQUIRE(data.data == + std::vector>{ + {{11.0f, 11.1f, 11.2f, 11.3f, 11.4f, 11.5f, 11.6f, 11.7f, 11.8f, 11.9f}, + {21.0f, 21.1f, 21.2f, 21.3f, 21.4f, 21.5f, 21.6f, 21.7f, 21.8f, 21.9f}}}); +} From 563bdda1a03853d36b80f452285f93766af5634d Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 22 Jun 2020 13:26:16 +0200 Subject: [PATCH 04/35] Throw on error --- include/bbp/sonata/optional.hpp | 1715 ++++++++++++++++++++++++++++ include/bbp/sonata/report_reader.h | 9 +- src/report_reader.cpp | 70 +- tests/test_report_reader.cpp | 20 +- 4 files changed, 1755 insertions(+), 59 deletions(-) create mode 100644 include/bbp/sonata/optional.hpp diff --git a/include/bbp/sonata/optional.hpp b/include/bbp/sonata/optional.hpp new file mode 100644 index 00000000..133673a9 --- /dev/null +++ b/include/bbp/sonata/optional.hpp @@ -0,0 +1,1715 @@ +// +// Copyright (c) 2014-2018 Martin Moene +// +// https://github.com/martinmoene/optional-lite +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#ifndef NONSTD_OPTIONAL_LITE_HPP +#define NONSTD_OPTIONAL_LITE_HPP + +#define optional_lite_MAJOR 3 +#define optional_lite_MINOR 2 +#define optional_lite_PATCH 0 + +#define optional_lite_VERSION optional_STRINGIFY(optional_lite_MAJOR) "." optional_STRINGIFY(optional_lite_MINOR) "." optional_STRINGIFY(optional_lite_PATCH) + +#define optional_STRINGIFY( x ) optional_STRINGIFY_( x ) +#define optional_STRINGIFY_( x ) #x + +// optional-lite configuration: + +#define optional_OPTIONAL_DEFAULT 0 +#define optional_OPTIONAL_NONSTD 1 +#define optional_OPTIONAL_STD 2 + +#if !defined( optional_CONFIG_SELECT_OPTIONAL ) +# define optional_CONFIG_SELECT_OPTIONAL ( optional_HAVE_STD_OPTIONAL ? optional_OPTIONAL_STD : optional_OPTIONAL_NONSTD ) +#endif + +// Control presence of exception handling (try and auto discover): + +#ifndef optional_CONFIG_NO_EXCEPTIONS +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +# define optional_CONFIG_NO_EXCEPTIONS 0 +# else +# define optional_CONFIG_NO_EXCEPTIONS 1 +# endif +#endif + +// C++ language version detection (C++20 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef optional_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define optional_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define optional_CPLUSPLUS __cplusplus +# endif +#endif + +#define optional_CPP98_OR_GREATER ( optional_CPLUSPLUS >= 199711L ) +#define optional_CPP11_OR_GREATER ( optional_CPLUSPLUS >= 201103L ) +#define optional_CPP11_OR_GREATER_ ( optional_CPLUSPLUS >= 201103L ) +#define optional_CPP14_OR_GREATER ( optional_CPLUSPLUS >= 201402L ) +#define optional_CPP17_OR_GREATER ( optional_CPLUSPLUS >= 201703L ) +#define optional_CPP20_OR_GREATER ( optional_CPLUSPLUS >= 202000L ) + +// C++ language version (represent 98 as 3): + +#define optional_CPLUSPLUS_V ( optional_CPLUSPLUS / 100 - (optional_CPLUSPLUS > 200000 ? 2000 : 1994) ) + +// Use C++17 std::optional if available and requested: + +#if optional_CPP17_OR_GREATER && defined(__has_include ) +# if __has_include( ) +# define optional_HAVE_STD_OPTIONAL 1 +# else +# define optional_HAVE_STD_OPTIONAL 0 +# endif +#else +# define optional_HAVE_STD_OPTIONAL 0 +#endif + +#define optional_USES_STD_OPTIONAL ( (optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_STD) || ((optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_DEFAULT) && optional_HAVE_STD_OPTIONAL) ) + +// +// in_place: code duplicated in any-lite, expected-lite, optional-lite, value-ptr-lite, variant-lite: +// + +#ifndef nonstd_lite_HAVE_IN_PLACE_TYPES +#define nonstd_lite_HAVE_IN_PLACE_TYPES 1 + +// C++17 std::in_place in : + +#if optional_CPP17_OR_GREATER + +#include + +namespace nonstd { + +using std::in_place; +using std::in_place_type; +using std::in_place_index; +using std::in_place_t; +using std::in_place_type_t; +using std::in_place_index_t; + +#define nonstd_lite_in_place_t( T) std::in_place_t +#define nonstd_lite_in_place_type_t( T) std::in_place_type_t +#define nonstd_lite_in_place_index_t(K) std::in_place_index_t + +#define nonstd_lite_in_place( T) std::in_place_t{} +#define nonstd_lite_in_place_type( T) std::in_place_type_t{} +#define nonstd_lite_in_place_index(K) std::in_place_index_t{} + +} // namespace nonstd + +#else // optional_CPP17_OR_GREATER + +#include + +namespace nonstd { +namespace detail { + +template< class T > +struct in_place_type_tag {}; + +template< std::size_t K > +struct in_place_index_tag {}; + +} // namespace detail + +struct in_place_t {}; + +template< class T > +inline in_place_t in_place( detail::in_place_type_tag /*unused*/ = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place( detail::in_place_index_tag /*unused*/ = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +template< class T > +inline in_place_t in_place_type( detail::in_place_type_tag /*unused*/ = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place_index( detail::in_place_index_tag /*unused*/ = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +// mimic templated typedef: + +#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag ) + +#define nonstd_lite_in_place( T) nonstd::in_place_type +#define nonstd_lite_in_place_type( T) nonstd::in_place_type +#define nonstd_lite_in_place_index(K) nonstd::in_place_index + +} // namespace nonstd + +#endif // optional_CPP17_OR_GREATER +#endif // nonstd_lite_HAVE_IN_PLACE_TYPES + +// +// Using std::optional: +// + +#if optional_USES_STD_OPTIONAL + +#include + +namespace nonstd { + + using std::optional; + using std::bad_optional_access; + using std::hash; + + using std::nullopt; + using std::nullopt_t; + + using std::operator==; + using std::operator!=; + using std::operator<; + using std::operator<=; + using std::operator>; + using std::operator>=; + using std::make_optional; + using std::swap; +} + +#else // optional_USES_STD_OPTIONAL + +#include +#include + +// optional-lite alignment configuration: + +#ifndef optional_CONFIG_MAX_ALIGN_HACK +# define optional_CONFIG_MAX_ALIGN_HACK 0 +#endif + +#ifndef optional_CONFIG_ALIGN_AS +// no default, used in #if defined() +#endif + +#ifndef optional_CONFIG_ALIGN_AS_FALLBACK +# define optional_CONFIG_ALIGN_AS_FALLBACK double +#endif + +// Compiler warning suppression: + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wundef" +#elif defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wundef" +#elif defined(_MSC_VER ) +# pragma warning( push ) +#endif + +// half-open range [lo..hi): +#define optional_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Compiler versions: +// +// MSVC++ 6.0 _MSC_VER == 1200 optional_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) +// MSVC++ 7.0 _MSC_VER == 1300 optional_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) +// MSVC++ 7.1 _MSC_VER == 1310 optional_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) +// MSVC++ 8.0 _MSC_VER == 1400 optional_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) +// MSVC++ 9.0 _MSC_VER == 1500 optional_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) +// MSVC++ 10.0 _MSC_VER == 1600 optional_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) +// MSVC++ 11.0 _MSC_VER == 1700 optional_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) +// MSVC++ 12.0 _MSC_VER == 1800 optional_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) +// MSVC++ 14.0 _MSC_VER == 1900 optional_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) +// MSVC++ 14.1 _MSC_VER >= 1910 optional_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) +// MSVC++ 14.2 _MSC_VER >= 1920 optional_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) + +#if defined(_MSC_VER ) && !defined(__clang__) +# define optional_COMPILER_MSVC_VER (_MSC_VER ) +# define optional_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) +#else +# define optional_COMPILER_MSVC_VER 0 +# define optional_COMPILER_MSVC_VERSION 0 +#endif + +#define optional_COMPILER_VERSION( major, minor, patch ) ( 10 * (10 * (major) + (minor) ) + (patch) ) + +#if defined(__GNUC__) && !defined(__clang__) +# define optional_COMPILER_GNUC_VERSION optional_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else +# define optional_COMPILER_GNUC_VERSION 0 +#endif + +#if defined(__clang__) +# define optional_COMPILER_CLANG_VERSION optional_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +# define optional_COMPILER_CLANG_VERSION 0 +#endif + +#if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 140 ) +# pragma warning( disable: 4345 ) // initialization behavior changed +#endif + +#if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 150 ) +# pragma warning( disable: 4814 ) // in C++14 'constexpr' will not imply 'const' +#endif + +// Presence of language and library features: + +#define optional_HAVE(FEATURE) ( optional_HAVE_##FEATURE ) + +#ifdef _HAS_CPP0X +# define optional_HAS_CPP0X _HAS_CPP0X +#else +# define optional_HAS_CPP0X 0 +#endif + +// Unless defined otherwise below, consider VC14 as C++11 for optional-lite: + +#if optional_COMPILER_MSVC_VER >= 1900 +# undef optional_CPP11_OR_GREATER +# define optional_CPP11_OR_GREATER 1 +#endif + +#define optional_CPP11_90 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1500) +#define optional_CPP11_100 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1600) +#define optional_CPP11_110 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1700) +#define optional_CPP11_120 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1800) +#define optional_CPP11_140 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1900) +#define optional_CPP11_141 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1910) + +#define optional_CPP11_140_490 ((optional_CPP11_OR_GREATER_ && optional_COMPILER_GNUC_VERSION >= 490) || (optional_COMPILER_MSVC_VER >= 1910)) + +#define optional_CPP14_000 (optional_CPP14_OR_GREATER) +#define optional_CPP17_000 (optional_CPP17_OR_GREATER) + +// Presence of C++11 language features: + +#define optional_HAVE_CONSTEXPR_11 optional_CPP11_140 +#define optional_HAVE_IS_DEFAULT optional_CPP11_140 +#define optional_HAVE_NOEXCEPT optional_CPP11_140 +#define optional_HAVE_NULLPTR optional_CPP11_100 +#define optional_HAVE_REF_QUALIFIER optional_CPP11_140_490 +#define optional_HAVE_INITIALIZER_LIST optional_CPP11_140 + +// Presence of C++14 language features: + +#define optional_HAVE_CONSTEXPR_14 optional_CPP14_000 + +// Presence of C++17 language features: + +#define optional_HAVE_NODISCARD optional_CPP17_000 + +// Presence of C++ library features: + +#define optional_HAVE_CONDITIONAL optional_CPP11_120 +#define optional_HAVE_REMOVE_CV optional_CPP11_120 +#define optional_HAVE_TYPE_TRAITS optional_CPP11_90 + +#define optional_HAVE_TR1_TYPE_TRAITS (!! optional_COMPILER_GNUC_VERSION ) +#define optional_HAVE_TR1_ADD_POINTER (!! optional_COMPILER_GNUC_VERSION ) + +// C++ feature usage: + +#if optional_HAVE( CONSTEXPR_11 ) +# define optional_constexpr constexpr +#else +# define optional_constexpr /*constexpr*/ +#endif + +#if optional_HAVE( IS_DEFAULT ) +# define optional_is_default = default; +#else +# define optional_is_default {} +#endif + +#if optional_HAVE( CONSTEXPR_14 ) +# define optional_constexpr14 constexpr +#else +# define optional_constexpr14 /*constexpr*/ +#endif + +#if optional_HAVE( NODISCARD ) +# define optional_nodiscard [[nodiscard]] +#else +# define optional_nodiscard /*[[nodiscard]]*/ +#endif + +#if optional_HAVE( NOEXCEPT ) +# define optional_noexcept noexcept +#else +# define optional_noexcept /*noexcept*/ +#endif + +#if optional_HAVE( NULLPTR ) +# define optional_nullptr nullptr +#else +# define optional_nullptr NULL +#endif + +#if optional_HAVE( REF_QUALIFIER ) +// NOLINTNEXTLINE( bugprone-macro-parentheses ) +# define optional_ref_qual & +# define optional_refref_qual && +#else +# define optional_ref_qual /*&*/ +# define optional_refref_qual /*&&*/ +#endif + +// additional includes: + +#if optional_CONFIG_NO_EXCEPTIONS +// already included: +#else +# include +#endif + +#if optional_CPP11_OR_GREATER +# include +#endif + +#if optional_HAVE( INITIALIZER_LIST ) +# include +#endif + +#if optional_HAVE( TYPE_TRAITS ) +# include +#elif optional_HAVE( TR1_TYPE_TRAITS ) +# include +#endif + +// Method enabling + +#if optional_CPP11_OR_GREATER + +#define optional_REQUIRES_0(...) \ + template< bool B = (__VA_ARGS__), typename std::enable_if::type = 0 > + +#define optional_REQUIRES_T(...) \ + , typename std::enable_if< (__VA_ARGS__), int >::type = 0 + +#define optional_REQUIRES_R(R, ...) \ + typename std::enable_if< (__VA_ARGS__), R>::type + +#define optional_REQUIRES_A(...) \ + , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr + +#endif + +// +// optional: +// + +namespace nonstd { namespace optional_lite { + +namespace std11 { + +#if optional_CPP11_OR_GREATER + using std::move; +#else + template< typename T > T & move( T & t ) { return t; } +#endif + +#if optional_HAVE( CONDITIONAL ) + using std::conditional; +#else + template< bool B, typename T, typename F > struct conditional { typedef T type; }; + template< typename T, typename F > struct conditional { typedef F type; }; +#endif // optional_HAVE_CONDITIONAL + +// gcc < 5: +#if optional_CPP11_OR_GREATER +#if optional_BETWEEN( optional_COMPILER_GNUC_VERSION, 1, 500 ) + template< typename T > struct is_trivially_copy_constructible : std::true_type{}; + template< typename T > struct is_trivially_move_constructible : std::true_type{}; +#else + using std::is_trivially_copy_constructible; + using std::is_trivially_move_constructible; +#endif +#endif +} // namespace std11 + +#if optional_CPP11_OR_GREATER + +/// type traits C++17: + +namespace std17 { + +#if optional_CPP17_OR_GREATER + +using std::is_swappable; +using std::is_nothrow_swappable; + +#elif optional_CPP11_OR_GREATER + +namespace detail { + +using std::swap; + +struct is_swappable +{ + template< typename T, typename = decltype( swap( std::declval(), std::declval() ) ) > + static std::true_type test( int /*unused*/ ); + + template< typename > + static std::false_type test(...); +}; + +struct is_nothrow_swappable +{ + // wrap noexcept(expr) in separate function as work-around for VC140 (VS2015): + + template< typename T > + static constexpr bool satisfies() + { + return noexcept( swap( std::declval(), std::declval() ) ); + } + + template< typename T > + static auto test( int /*unused*/ ) -> std::integral_constant()>{} + + template< typename > + static auto test(...) -> std::false_type; +}; + +} // namespace detail + +// is [nothow] swappable: + +template< typename T > +struct is_swappable : decltype( detail::is_swappable::test(0) ){}; + +template< typename T > +struct is_nothrow_swappable : decltype( detail::is_nothrow_swappable::test(0) ){}; + +#endif // optional_CPP17_OR_GREATER + +} // namespace std17 + +/// type traits C++20: + +namespace std20 { + +template< typename T > +struct remove_cvref +{ + typedef typename std::remove_cv< typename std::remove_reference::type >::type type; +}; + +} // namespace std20 + +#endif // optional_CPP11_OR_GREATER + +/// class optional + +template< typename T > +class optional; + +namespace detail { + +// C++11 emulation: + +struct nulltype{}; + +template< typename Head, typename Tail > +struct typelist +{ + typedef Head head; + typedef Tail tail; +}; + +#if optional_CONFIG_MAX_ALIGN_HACK + +// Max align, use most restricted type for alignment: + +#define optional_UNIQUE( name ) optional_UNIQUE2( name, __LINE__ ) +#define optional_UNIQUE2( name, line ) optional_UNIQUE3( name, line ) +#define optional_UNIQUE3( name, line ) name ## line + +#define optional_ALIGN_TYPE( type ) \ + type optional_UNIQUE( _t ); struct_t< type > optional_UNIQUE( _st ) + +template< typename T > +struct struct_t { T _; }; + +union max_align_t +{ + optional_ALIGN_TYPE( char ); + optional_ALIGN_TYPE( short int ); + optional_ALIGN_TYPE( int ); + optional_ALIGN_TYPE( long int ); + optional_ALIGN_TYPE( float ); + optional_ALIGN_TYPE( double ); + optional_ALIGN_TYPE( long double ); + optional_ALIGN_TYPE( char * ); + optional_ALIGN_TYPE( short int * ); + optional_ALIGN_TYPE( int * ); + optional_ALIGN_TYPE( long int * ); + optional_ALIGN_TYPE( float * ); + optional_ALIGN_TYPE( double * ); + optional_ALIGN_TYPE( long double * ); + optional_ALIGN_TYPE( void * ); + +#ifdef HAVE_LONG_LONG + optional_ALIGN_TYPE( long long ); +#endif + + struct Unknown; + + Unknown ( * optional_UNIQUE(_) )( Unknown ); + Unknown * Unknown::* optional_UNIQUE(_); + Unknown ( Unknown::* optional_UNIQUE(_) )( Unknown ); + + struct_t< Unknown ( * )( Unknown) > optional_UNIQUE(_); + struct_t< Unknown * Unknown::* > optional_UNIQUE(_); + struct_t< Unknown ( Unknown::* )(Unknown) > optional_UNIQUE(_); +}; + +#undef optional_UNIQUE +#undef optional_UNIQUE2 +#undef optional_UNIQUE3 + +#undef optional_ALIGN_TYPE + +#elif defined( optional_CONFIG_ALIGN_AS ) // optional_CONFIG_MAX_ALIGN_HACK + +// Use user-specified type for alignment: + +#define optional_ALIGN_AS( unused ) \ + optional_CONFIG_ALIGN_AS + +#else // optional_CONFIG_MAX_ALIGN_HACK + +// Determine POD type to use for alignment: + +#define optional_ALIGN_AS( to_align ) \ + typename type_of_size< alignment_types, alignment_of< to_align >::value >::type + +template< typename T > +struct alignment_of; + +template< typename T > +struct alignment_of_hack +{ + char c; + T t; + alignment_of_hack(); +}; + +template< size_t A, size_t S > +struct alignment_logic +{ + enum { value = A < S ? A : S }; +}; + +template< typename T > +struct alignment_of +{ + enum { value = alignment_logic< + sizeof( alignment_of_hack ) - sizeof(T), sizeof(T) >::value }; +}; + +template< typename List, size_t N > +struct type_of_size +{ + typedef typename std11::conditional< + N == sizeof( typename List::head ), + typename List::head, + typename type_of_size::type >::type type; +}; + +template< size_t N > +struct type_of_size< nulltype, N > +{ + typedef optional_CONFIG_ALIGN_AS_FALLBACK type; +}; + +template< typename T> +struct struct_t { T _; }; + +#define optional_ALIGN_TYPE( type ) \ + typelist< type , typelist< struct_t< type > + +struct Unknown; + +typedef + optional_ALIGN_TYPE( char ), + optional_ALIGN_TYPE( short ), + optional_ALIGN_TYPE( int ), + optional_ALIGN_TYPE( long ), + optional_ALIGN_TYPE( float ), + optional_ALIGN_TYPE( double ), + optional_ALIGN_TYPE( long double ), + + optional_ALIGN_TYPE( char *), + optional_ALIGN_TYPE( short * ), + optional_ALIGN_TYPE( int * ), + optional_ALIGN_TYPE( long * ), + optional_ALIGN_TYPE( float * ), + optional_ALIGN_TYPE( double * ), + optional_ALIGN_TYPE( long double * ), + + optional_ALIGN_TYPE( Unknown ( * )( Unknown ) ), + optional_ALIGN_TYPE( Unknown * Unknown::* ), + optional_ALIGN_TYPE( Unknown ( Unknown::* )( Unknown ) ), + + nulltype + > > > > > > > > > > > > > > + > > > > > > > > > > > > > > + > > > > > > + alignment_types; + +#undef optional_ALIGN_TYPE + +#endif // optional_CONFIG_MAX_ALIGN_HACK + +/// C++03 constructed union to hold value. + +template< typename T > +union storage_t +{ +//private: +// template< typename > friend class optional; + + typedef T value_type; + + storage_t() optional_is_default + + explicit storage_t( value_type const & v ) + { + construct_value( v ); + } + + void construct_value( value_type const & v ) + { + ::new( value_ptr() ) value_type( v ); + } + +#if optional_CPP11_OR_GREATER + + explicit storage_t( value_type && v ) + { + construct_value( std::move( v ) ); + } + + void construct_value( value_type && v ) + { + ::new( value_ptr() ) value_type( std::move( v ) ); + } + + template< class... Args > + void emplace( Args&&... args ) + { + ::new( value_ptr() ) value_type( std::forward(args)... ); + } + + template< class U, class... Args > + void emplace( std::initializer_list il, Args&&... args ) + { + ::new( value_ptr() ) value_type( il, std::forward(args)... ); + } + +#endif + + void destruct_value() + { + value_ptr()->~T(); + } + + optional_nodiscard value_type const * value_ptr() const + { + return as(); + } + + value_type * value_ptr() + { + return as(); + } + + optional_nodiscard value_type const & value() const optional_ref_qual + { + return * value_ptr(); + } + + value_type & value() optional_ref_qual + { + return * value_ptr(); + } + +#if optional_HAVE( REF_QUALIFIER ) + + optional_nodiscard value_type const && value() const optional_refref_qual + { + return std::move( value() ); + } + + value_type && value() optional_refref_qual + { + return std::move( value() ); + } + +#endif + +#if optional_CPP11_OR_GREATER + + using aligned_storage_t = typename std::aligned_storage< sizeof(value_type), alignof(value_type) >::type; + aligned_storage_t data; + +#elif optional_CONFIG_MAX_ALIGN_HACK + + typedef struct { unsigned char data[ sizeof(value_type) ]; } aligned_storage_t; + + max_align_t hack; + aligned_storage_t data; + +#else + typedef optional_ALIGN_AS(value_type) align_as_type; + + typedef struct { align_as_type data[ 1 + ( sizeof(value_type) - 1 ) / sizeof(align_as_type) ]; } aligned_storage_t; + aligned_storage_t data; + +# undef optional_ALIGN_AS + +#endif // optional_CONFIG_MAX_ALIGN_HACK + + optional_nodiscard void * ptr() optional_noexcept + { + return &data; + } + + optional_nodiscard void const * ptr() const optional_noexcept + { + return &data; + } + + template + optional_nodiscard U * as() + { + return reinterpret_cast( ptr() ); + } + + template + optional_nodiscard U const * as() const + { + return reinterpret_cast( ptr() ); + } +}; + +} // namespace detail + +/// disengaged state tag + +struct nullopt_t +{ + struct init{}; + explicit optional_constexpr nullopt_t( init /*unused*/ ) optional_noexcept {} +}; + +#if optional_HAVE( CONSTEXPR_11 ) +constexpr nullopt_t nullopt{ nullopt_t::init{} }; +#else +// extra parenthesis to prevent the most vexing parse: +const nullopt_t nullopt(( nullopt_t::init() )); +#endif + +/// optional access error + +#if ! optional_CONFIG_NO_EXCEPTIONS + +class bad_optional_access : public std::logic_error +{ +public: + explicit bad_optional_access() + : logic_error( "bad optional access" ) {} +}; + +#endif //optional_CONFIG_NO_EXCEPTIONS + +/// optional + +template< typename T> +class optional +{ +private: + template< typename > friend class optional; + + typedef void (optional::*safe_bool)() const; + +public: + typedef T value_type; + + // x.x.3.1, constructors + + // 1a - default construct + optional_constexpr optional() optional_noexcept + : has_value_( false ) + , contained() + {} + + // 1b - construct explicitly empty + // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) + optional_constexpr optional( nullopt_t /*unused*/ ) optional_noexcept + : has_value_( false ) + , contained() + {} + + // 2 - copy-construct +#if optional_CPP11_OR_GREATER + // template< typename U = T + // optional_REQUIRES_T( + // std::is_copy_constructible::value + // || std11::is_trivially_copy_constructible::value + // ) + // > +#endif + optional_constexpr14 optional( optional const & other ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( other.contained.value() ); + } + } + +#if optional_CPP11_OR_GREATER + + // 3 (C++11) - move-construct from optional + template< typename U = T + optional_REQUIRES_T( + std::is_move_constructible::value + || std11::is_trivially_move_constructible::value + ) + > + optional_constexpr14 optional( optional && other ) + // NOLINTNEXTLINE( performance-noexcept-move-constructor ) + noexcept( std::is_nothrow_move_constructible::value ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( std::move( other.contained.value() ) ); + } + } + + // 4a (C++11) - explicit converting copy-construct from optional + template< typename U + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && !std::is_convertible< U const & , T>::value /*=> explicit */ + ) + > + explicit optional( optional const & other ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( T{ other.contained.value() } ); + } + } +#endif // optional_CPP11_OR_GREATER + + // 4b (C++98 and later) - non-explicit converting copy-construct from optional + template< typename U +#if optional_CPP11_OR_GREATER + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && std::is_convertible< U const & , T>::value /*=> non-explicit */ + ) +#endif // optional_CPP11_OR_GREATER + > + // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) + /*non-explicit*/ optional( optional const & other ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( other.contained.value() ); + } + } + +#if optional_CPP11_OR_GREATER + + // 5a (C++11) - explicit converting move-construct from optional + template< typename U + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && !std::is_convertible< U &&, T>::value /*=> explicit */ + ) + > + explicit optional( optional && other + ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( T{ std::move( other.contained.value() ) } ); + } + } + + // 5a (C++11) - non-explicit converting move-construct from optional + template< typename U + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && std::is_convertible< U &&, T>::value /*=> non-explicit */ + ) + > + // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) + /*non-explicit*/ optional( optional && other ) + : has_value_( other.has_value() ) + { + if ( other.has_value() ) + { + contained.construct_value( std::move( other.contained.value() ) ); + } + } + + // 6 (C++11) - in-place construct + template< typename... Args + optional_REQUIRES_T( + std::is_constructible::value + ) + > + optional_constexpr explicit optional( nonstd_lite_in_place_t(T), Args&&... args ) + : has_value_( true ) + , contained( T( std::forward(args)...) ) + {} + + // 7 (C++11) - in-place construct, initializer-list + template< typename U, typename... Args + optional_REQUIRES_T( + std::is_constructible&, Args&&...>::value + ) + > + optional_constexpr explicit optional( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) + : has_value_( true ) + , contained( T( il, std::forward(args)...) ) + {} + + // 8a (C++11) - explicit move construct from value + template< typename U = T + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_same::type, nonstd_lite_in_place_t(U)>::value + && !std::is_same::type, optional>::value + && !std::is_convertible::value /*=> explicit */ + ) + > + optional_constexpr explicit optional( U && value ) + : has_value_( true ) + , contained( T{ std::forward( value ) } ) + {} + + // 8b (C++11) - non-explicit move construct from value + template< typename U = T + optional_REQUIRES_T( + std::is_constructible::value + && !std::is_same::type, nonstd_lite_in_place_t(U)>::value + && !std::is_same::type, optional>::value + && std::is_convertible::value /*=> non-explicit */ + ) + > + // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) + optional_constexpr /*non-explicit*/ optional( U && value ) + : has_value_( true ) + , contained( std::forward( value ) ) + {} + +#else // optional_CPP11_OR_GREATER + + // 8 (C++98) + optional( value_type const & value ) + : has_value_( true ) + , contained( value ) + {} + +#endif // optional_CPP11_OR_GREATER + + // x.x.3.2, destructor + + ~optional() + { + if ( has_value() ) + { + contained.destruct_value(); + } + } + + // x.x.3.3, assignment + + // 1 (C++98and later) - assign explicitly empty + optional & operator=( nullopt_t /*unused*/) optional_noexcept + { + reset(); + return *this; + } + + // 2 (C++98and later) - copy-assign from optional +#if optional_CPP11_OR_GREATER + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional &, + true +// std::is_copy_constructible::value +// && std::is_copy_assignable::value + ) + operator=( optional const & other ) + noexcept( + std::is_nothrow_move_assignable::value + && std::is_nothrow_move_constructible::value + ) +#else + optional & operator=( optional const & other ) +#endif + { + if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); } + else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( *other ); } + else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = *other; } + return *this; + } + +#if optional_CPP11_OR_GREATER + + // 3 (C++11) - move-assign from optional + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional &, + true +// std::is_move_constructible::value +// && std::is_move_assignable::value + ) + operator=( optional && other ) noexcept + { + if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); } + else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std::move( *other ) ); } + else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = std::move( *other ); } + return *this; + } + + // 4 (C++11) - move-assign from value + template< typename U = T > + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional &, + std::is_constructible::value + && std::is_assignable::value + && !std::is_same::type, nonstd_lite_in_place_t(U)>::value + && !std::is_same::type, optional>::value + && !(std::is_scalar::value && std::is_same::type>::value) + ) + operator=( U && value ) + { + if ( has_value() ) + { + contained.value() = std::forward( value ); + } + else + { + initialize( T( std::forward( value ) ) ); + } + return *this; + } + +#else // optional_CPP11_OR_GREATER + + // 4 (C++98) - copy-assign from value + template< typename U /*= T*/ > + optional & operator=( U const & value ) + { + if ( has_value() ) contained.value() = value; + else initialize( T( value ) ); + return *this; + } + +#endif // optional_CPP11_OR_GREATER + + // 5 (C++98 and later) - converting copy-assign from optional + template< typename U > +#if optional_CPP11_OR_GREATER + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional&, + std::is_constructible< T , U const &>::value + && std::is_assignable< T&, U const &>::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && !std::is_assignable< T&, optional & >::value + && !std::is_assignable< T&, optional && >::value + && !std::is_assignable< T&, optional const & >::value + && !std::is_assignable< T&, optional const && >::value + ) +#else + optional& +#endif // optional_CPP11_OR_GREATER + operator=( optional const & other ) + { + return *this = optional( other ); + } + +#if optional_CPP11_OR_GREATER + + // 6 (C++11) - converting move-assign from optional + template< typename U > + // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) + optional_REQUIRES_R( + optional&, + std::is_constructible< T , U>::value + && std::is_assignable< T&, U>::value + && !std::is_constructible & >::value + && !std::is_constructible && >::value + && !std::is_constructible const & >::value + && !std::is_constructible const && >::value + && !std::is_convertible< optional & , T>::value + && !std::is_convertible< optional && , T>::value + && !std::is_convertible< optional const & , T>::value + && !std::is_convertible< optional const &&, T>::value + && !std::is_assignable< T&, optional & >::value + && !std::is_assignable< T&, optional && >::value + && !std::is_assignable< T&, optional const & >::value + && !std::is_assignable< T&, optional const && >::value + ) + operator=( optional && other ) + { + return *this = optional( std::move( other ) ); + } + + // 7 (C++11) - emplace + template< typename... Args + optional_REQUIRES_T( + std::is_constructible::value + ) + > + T& emplace( Args&&... args ) + { + *this = nullopt; + contained.emplace( std::forward(args)... ); + has_value_ = true; + return contained.value(); + } + + // 8 (C++11) - emplace, initializer-list + template< typename U, typename... Args + optional_REQUIRES_T( + std::is_constructible&, Args&&...>::value + ) + > + T& emplace( std::initializer_list il, Args&&... args ) + { + *this = nullopt; + contained.emplace( il, std::forward(args)... ); + has_value_ = true; + return contained.value(); + } + +#endif // optional_CPP11_OR_GREATER + + // x.x.3.4, swap + + void swap( optional & other ) +#if optional_CPP11_OR_GREATER + noexcept( + std::is_nothrow_move_constructible::value + && std17::is_nothrow_swappable::value + ) +#endif + { + using std::swap; + if ( (has_value() == true ) && (other.has_value() == true ) ) { swap( **this, *other ); } + else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std11::move(*other) ); other.reset(); } + else if ( (has_value() == true ) && (other.has_value() == false) ) { other.initialize( std11::move(**this) ); reset(); } + } + + // x.x.3.5, observers + + optional_constexpr value_type const * operator ->() const + { + return assert( has_value() ), + contained.value_ptr(); + } + + optional_constexpr14 value_type * operator ->() + { + return assert( has_value() ), + contained.value_ptr(); + } + + optional_constexpr value_type const & operator *() const optional_ref_qual + { + return assert( has_value() ), + contained.value(); + } + + optional_constexpr14 value_type & operator *() optional_ref_qual + { + return assert( has_value() ), + contained.value(); + } + +#if optional_HAVE( REF_QUALIFIER ) + + optional_constexpr value_type const && operator *() const optional_refref_qual + { + return std::move( **this ); + } + + optional_constexpr14 value_type && operator *() optional_refref_qual + { + return std::move( **this ); + } + +#endif + +#if optional_CPP11_OR_GREATER + optional_constexpr explicit operator bool() const optional_noexcept + { + return has_value(); + } +#else + optional_constexpr operator safe_bool() const optional_noexcept + { + return has_value() ? &optional::this_type_does_not_support_comparisons : 0; + } +#endif + + // NOLINTNEXTLINE( modernize-use-nodiscard ) + /*optional_nodiscard*/ optional_constexpr bool has_value() const optional_noexcept + { + return has_value_; + } + + // NOLINTNEXTLINE( modernize-use-nodiscard ) + /*optional_nodiscard*/ optional_constexpr14 value_type const & value() const optional_ref_qual + { +#if optional_CONFIG_NO_EXCEPTIONS + assert( has_value() ); +#else + if ( ! has_value() ) + { + throw bad_optional_access(); + } +#endif + return contained.value(); + } + + optional_constexpr14 value_type & value() optional_ref_qual + { +#if optional_CONFIG_NO_EXCEPTIONS + assert( has_value() ); +#else + if ( ! has_value() ) + { + throw bad_optional_access(); + } +#endif + return contained.value(); + } + +#if optional_HAVE( REF_QUALIFIER ) && ( !optional_COMPILER_GNUC_VERSION || optional_COMPILER_GNUC_VERSION >= 490 ) + + // NOLINTNEXTLINE( modernize-use-nodiscard ) + /*optional_nodiscard*/ optional_constexpr value_type const && value() const optional_refref_qual + { + return std::move( value() ); + } + + optional_constexpr14 value_type && value() optional_refref_qual + { + return std::move( value() ); + } + +#endif + +#if optional_CPP11_OR_GREATER + + template< typename U > + optional_constexpr value_type value_or( U && v ) const optional_ref_qual + { + return has_value() ? contained.value() : static_cast(std::forward( v ) ); + } + + template< typename U > + optional_constexpr14 value_type value_or( U && v ) optional_refref_qual + { + return has_value() ? std::move( contained.value() ) : static_cast(std::forward( v ) ); + } + +#else + + template< typename U > + optional_constexpr value_type value_or( U const & v ) const + { + return has_value() ? contained.value() : static_cast( v ); + } + +#endif // optional_CPP11_OR_GREATER + + // x.x.3.6, modifiers + + void reset() optional_noexcept + { + if ( has_value() ) + { + contained.destruct_value(); + } + + has_value_ = false; + } + +private: + void this_type_does_not_support_comparisons() const {} + + template< typename V > + void initialize( V const & value ) + { + assert( ! has_value() ); + contained.construct_value( value ); + has_value_ = true; + } + +#if optional_CPP11_OR_GREATER + template< typename V > + void initialize( V && value ) + { + assert( ! has_value() ); + contained.construct_value( std::move( value ) ); + has_value_ = true; + } + +#endif + +private: + bool has_value_; + detail::storage_t< value_type > contained; + +}; + +// Relational operators + +template< typename T, typename U > +inline optional_constexpr bool operator==( optional const & x, optional const & y ) +{ + return bool(x) != bool(y) ? false : !bool( x ) ? true : *x == *y; +} + +template< typename T, typename U > +inline optional_constexpr bool operator!=( optional const & x, optional const & y ) +{ + return !(x == y); +} + +template< typename T, typename U > +inline optional_constexpr bool operator<( optional const & x, optional const & y ) +{ + return (!y) ? false : (!x) ? true : *x < *y; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>( optional const & x, optional const & y ) +{ + return (y < x); +} + +template< typename T, typename U > +inline optional_constexpr bool operator<=( optional const & x, optional const & y ) +{ + return !(y < x); +} + +template< typename T, typename U > +inline optional_constexpr bool operator>=( optional const & x, optional const & y ) +{ + return !(x < y); +} + +// Comparison with nullopt + +template< typename T > +inline optional_constexpr bool operator==( optional const & x, nullopt_t /*unused*/ ) optional_noexcept +{ + return (!x); +} + +template< typename T > +inline optional_constexpr bool operator==( nullopt_t /*unused*/, optional const & x ) optional_noexcept +{ + return (!x); +} + +template< typename T > +inline optional_constexpr bool operator!=( optional const & x, nullopt_t /*unused*/ ) optional_noexcept +{ + return bool(x); +} + +template< typename T > +inline optional_constexpr bool operator!=( nullopt_t /*unused*/, optional const & x ) optional_noexcept +{ + return bool(x); +} + +template< typename T > +inline optional_constexpr bool operator<( optional const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept +{ + return false; +} + +template< typename T > +inline optional_constexpr bool operator<( nullopt_t /*unused*/, optional const & x ) optional_noexcept +{ + return bool(x); +} + +template< typename T > +inline optional_constexpr bool operator<=( optional const & x, nullopt_t /*unused*/ ) optional_noexcept +{ + return (!x); +} + +template< typename T > +inline optional_constexpr bool operator<=( nullopt_t /*unused*/, optional const & /*unused*/ ) optional_noexcept +{ + return true; +} + +template< typename T > +inline optional_constexpr bool operator>( optional const & x, nullopt_t /*unused*/ ) optional_noexcept +{ + return bool(x); +} + +template< typename T > +inline optional_constexpr bool operator>( nullopt_t /*unused*/, optional const & /*unused*/ ) optional_noexcept +{ + return false; +} + +template< typename T > +inline optional_constexpr bool operator>=( optional const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept +{ + return true; +} + +template< typename T > +inline optional_constexpr bool operator>=( nullopt_t /*unused*/, optional const & x ) optional_noexcept +{ + return (!x); +} + +// Comparison with T + +template< typename T, typename U > +inline optional_constexpr bool operator==( optional const & x, U const & v ) +{ + return bool(x) ? *x == v : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator==( U const & v, optional const & x ) +{ + return bool(x) ? v == *x : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator!=( optional const & x, U const & v ) +{ + return bool(x) ? *x != v : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator!=( U const & v, optional const & x ) +{ + return bool(x) ? v != *x : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator<( optional const & x, U const & v ) +{ + return bool(x) ? *x < v : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator<( U const & v, optional const & x ) +{ + return bool(x) ? v < *x : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator<=( optional const & x, U const & v ) +{ + return bool(x) ? *x <= v : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator<=( U const & v, optional const & x ) +{ + return bool(x) ? v <= *x : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>( optional const & x, U const & v ) +{ + return bool(x) ? *x > v : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>( U const & v, optional const & x ) +{ + return bool(x) ? v > *x : true; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>=( optional const & x, U const & v ) +{ + return bool(x) ? *x >= v : false; +} + +template< typename T, typename U > +inline optional_constexpr bool operator>=( U const & v, optional const & x ) +{ + return bool(x) ? v >= *x : true; +} + +// Specialized algorithms + +template< typename T +#if optional_CPP11_OR_GREATER + optional_REQUIRES_T( + std::is_move_constructible::value + && std17::is_swappable::value ) +#endif +> +void swap( optional & x, optional & y ) +#if optional_CPP11_OR_GREATER + noexcept( noexcept( x.swap(y) ) ) +#endif +{ + x.swap( y ); +} + +#if optional_CPP11_OR_GREATER + +template< typename T > +optional_constexpr optional< typename std::decay::type > make_optional( T && value ) +{ + return optional< typename std::decay::type >( std::forward( value ) ); +} + +template< typename T, typename...Args > +optional_constexpr optional make_optional( Args&&... args ) +{ + return optional( nonstd_lite_in_place(T), std::forward(args)...); +} + +template< typename T, typename U, typename... Args > +optional_constexpr optional make_optional( std::initializer_list il, Args&&... args ) +{ + return optional( nonstd_lite_in_place(T), il, std::forward(args)...); +} + +#else + +template< typename T > +optional make_optional( T const & value ) +{ + return optional( value ); +} + +#endif // optional_CPP11_OR_GREATER + +} // namespace optional_lite + +using optional_lite::optional; +using optional_lite::nullopt_t; +using optional_lite::nullopt; + +#if ! optional_CONFIG_NO_EXCEPTIONS +using optional_lite::bad_optional_access; +#endif + +using optional_lite::make_optional; + +} // namespace nonstd + +#if optional_CPP11_OR_GREATER + +// specialize the std::hash algorithm: + +namespace std { + +template< class T > +struct hash< nonstd::optional > +{ +public: + std::size_t operator()( nonstd::optional const & v ) const optional_noexcept + { + return bool( v ) ? std::hash{}( *v ) : 0; + } +}; + +} //namespace std + +#endif // optional_CPP11_OR_GREATER + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# pragma GCC diagnostic pop +#elif defined(_MSC_VER ) +# pragma warning( pop ) +#endif + +#endif // optional_USES_STD_OPTIONAL + +#endif // NONSTD_OPTIONAL_LITE_HPP diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index 8691f31d..50ea2368 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -9,6 +9,7 @@ #include #include +#include namespace H5 = HighFive; @@ -94,13 +95,13 @@ class SONATA_API ReportReader std::string getDataUnits() const; bool getSorted() const; - DataFrame get(const Selection& nodes_ids = Selection({}), - double _tstart = -1, - double _tstop = -1) const; + DataFrame get(const nonstd::optional& nodes_ids = nonstd::nullopt, + const nonstd::optional& _tstart = nonstd::nullopt, + const nonstd::optional& _tstop = nonstd::nullopt) const; private: Population(const H5::File& file, const std::string& populationName); - std::pair getIndex(double tstart, double tstop) const; + std::pair getIndex(const nonstd::optional& tstart, const nonstd::optional& tstop) const; std::vector>> nodes_pointers_; H5::Group pop_group_; diff --git a/src/report_reader.cpp b/src/report_reader.cpp index e8ce443a..f8f73581 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -225,8 +225,8 @@ ReportReader::Population::Population(const H5::File& file, const std::string& tstep_ = times[2]; mapping_group.getDataSet("time").getAttribute("units").read(time_units_); size_t i = 0; - for (double t = tstart_; t < tstop_ - EPSILON; t += tstep_) { - times_index_.push_back({i++, t}); + for (double t = tstart_; t < tstop_ - EPSILON; t += tstep_, ++i) { + times_index_.emplace_back(i, t); } } @@ -261,57 +261,49 @@ bool ReportReader::Population::getSorted() const { } template -std::pair ReportReader::Population::getIndex(double tstart, double tstop) const { +std::pair ReportReader::Population::getIndex(const nonstd::optional& tstart, const nonstd::optional& tstop) const { std::pair indexes; - if (tstart < 0 - EPSILON) { // Default value - indexes.first = times_index_.front().first; - } else if (tstart > times_index_.back().second + EPSILON) { // tstart is after end of range - indexes.first = 1; - indexes.second = 0; - return indexes; - } else { - for (const auto& time_index : times_index_) { - if (tstart < time_index.second + EPSILON) { - indexes.first = time_index.first; - break; - } - } + + double start = tstart.value_or(tstart_); + double stop = tstop.value_or(tstop_); + + if (start < 0 - EPSILON || stop < 0 - EPSILON) { + throw std::runtime_error("Times cannot be negative"); } - if (tstop < 0 - EPSILON) { // Default value - indexes.second = times_index_.back().first; - } else if (tstop < times_index_.front().second - EPSILON) { // tstop is before start of range - indexes.first = 1; - indexes.second = 0; - return indexes; - } else { - for (auto it = times_index_.crbegin(); it != times_index_.crend(); ++it) { - const auto& time_index = *it; - if (tstop > time_index.second - EPSILON) { - indexes.second = time_index.first; - break; - } - } + + auto it_start = std::find_if(times_index_.cbegin(), times_index_.cend(), + [&](const std::pair& v) { + return start < v.second + EPSILON; + }); + if (it_start == times_index_.end()) { + throw std::runtime_error("tstart is after the end of the range"); } + indexes.first = it_start->first; - if (indexes.first > indexes.second) { - indexes.second = indexes.first; + auto it_stop = std::find_if(times_index_.crbegin(), times_index_.crend(), + [&](const std::pair& v) { + return stop > v.second - EPSILON; + }); + if (it_stop == times_index_.rend()) { + throw std::runtime_error("tstop is before the beginning of the range"); } + indexes.second = it_stop->first; return indexes; } template -DataFrame ReportReader::Population::get(const Selection& selection, - double tstart, - double tstop) const { +DataFrame ReportReader::Population::get(const nonstd::optional& selection, + const nonstd::optional& tstart, + const nonstd::optional& tstop) const { DataFrame data_frame; size_t index_start = 0; size_t index_stop = 0; std::tie(index_start, index_stop) = getIndex(tstart, tstop); - if (index_start > index_stop) { - return data_frame; + if (index_start >= index_stop) { + throw std::runtime_error("tstart should be < to tstop"); } for (size_t i = index_start; i <= index_stop; ++i) { @@ -324,7 +316,7 @@ DataFrame ReportReader::Population::get(const Selection& selection, // auto nodes_ids_ = Selection::fromValues(nodes_ids.flatten().sort()); Selection::Values node_ids; - if (selection.empty()) { // Take all nodes in this case + if (!selection || selection->empty()) { // Take all nodes in this case std::transform(nodes_pointers_.begin(), nodes_pointers_.end(), std::back_inserter(node_ids), @@ -332,7 +324,7 @@ DataFrame ReportReader::Population::get(const Selection& selection, return node_pointer.first; }); } else { - node_ids = selection.flatten(); + node_ids = selection->flatten(); } data_frame.data.resize(index_stop - index_start + 1); diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index a4d2f258..7f0cd707 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -38,10 +38,7 @@ TEST_CASE("SomaReportReader limits", "[base]") { auto pop = reader.openPopulation("All"); // ids out of range - REQUIRE(pop.get(Selection({{100, 101}})).ids == DataFrame::DataType()); - testTimes(pop.get(Selection({{100, 101}})).times, 0.0, 0.1, 10); - REQUIRE(pop.get(Selection({{100, 101}})).data == - std::vector>({{}, {}, {}, {}, {}, {}, {}, {}, {}, {}})); + // REQUIRE_THROWS(pop.get(Selection({{100, 101}}))); // Inverted id REQUIRE_THROWS(pop.get(Selection({{2, 1}}))); @@ -50,22 +47,13 @@ TEST_CASE("SomaReportReader limits", "[base]") { REQUIRE_THROWS(pop.get(Selection({{-1, 1}}))); // Times out of range - REQUIRE(pop.get(Selection({{1, 2}}), 100., 101.).ids == DataFrame::DataType()); - testTimes(pop.get(Selection({{1, 2}}), 100., 101.).times, 0.0, 0.0, 0); - REQUIRE(pop.get(Selection({{1, 2}}), 100., 101.).data == std::vector>({})); + REQUIRE_THROWS(pop.get(Selection({{1, 2}}), 100., 101.)); // Inverted times - REQUIRE(pop.get(Selection({{1, 2}}), 0.2, 0.1).ids == DataFrame::DataType({1})); - testTimes(pop.get(Selection({{1, 2}}), 0.2, 0.1).times, 0.2, 0.1, 1); - REQUIRE(pop.get(Selection({{1, 2}}), 0.2, 0.1).data == - std::vector>({{1.2}})); + // REQUIRE_THROWS(pop.get(Selection({{1, 2}}), 0.2, 0.1)); // Negatives times - REQUIRE(pop.get(Selection({{1, 2}}), -1., -2.).ids == DataFrame::DataType({1})); - testTimes(pop.get(Selection({{1, 2}}), -1., -2.).times, 0.0, 0.1, 10); - REQUIRE(pop.get(Selection({{1, 2}}), -1., -2.).data == - std::vector>( - {{1.0}, {1.1}, {1.2}, {1.3}, {1.4}, {1.5}, {1.6}, {1.7}, {1.8}, {1.9}})); + REQUIRE_THROWS(pop.get(Selection({{1, 2}}), -1., -2.)); } TEST_CASE("SomaReportReader", "[base]") { From 19e660af1baad749a14b575acc656d47f78f114c Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 22 Jun 2020 18:15:34 +0200 Subject: [PATCH 05/35] Fix python for nonstd::nullopt_t --- python/bindings.cpp | 33 +++++++++++---------------------- python/tests/test.py | 7 ++----- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/python/bindings.cpp b/python/bindings.cpp index 2135c815..22d40126 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -304,6 +304,14 @@ py::class_ bindStorageClass(py::module& m, const char* clsName, const c } } // unnamed namespace +namespace pybind11 { namespace detail { + template + struct type_caster> : optional_caster> {}; + + template<> struct type_caster + : public void_caster {}; +}} + template void bindReportReader(py::module& m, const std::string& prefix) { py::class_>(m, @@ -315,31 +323,12 @@ void bindReportReader(py::module& m, const std::string& prefix) { py::class_(m, (prefix + "ReportPopulation").c_str(), "A population inside a ReportReader") - .def( - "get", - [](const typename ReportType::Population& self) { return self.get(); }, - "Return all reports") - .def( - "get", - [](const typename ReportType::Population& self, double tstart, double tstop) { - return self.get(Selection({}), tstart, tstop); - }, - "Return reports between 'tstart' and 'tstop'", - "tstart"_a, - "tstop"_a) - .def( - "get", - [](const typename ReportType::Population& self, Selection sel) { - return self.get(sel); - }, - "Return reports with all those node_ids", - "node_ids"_a) .def("get", &ReportType::Population::get, "Return reports with all those node_ids between 'tstart' and 'tstop'", - "node_ids"_a, - "tstart"_a, - "tstop"_a) + "node_ids"_a = nonstd::nullopt, + "tstart"_a = nonstd::nullopt, + "tstop"_a = nonstd::nullopt) .def_property_readonly("sorted", &ReportType::Population::getSorted, "Return if data are sorted") diff --git a/python/tests/test.py b/python/tests/test.py index e36c607a..49de4028 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -308,11 +308,8 @@ def test_get_reports_from_population(self): self.assertEqual(keys, [(13, 30), (13, 30), (13, 31), (13, 31), (13, 32), (14, 32), (14, 33), (14, 33), (14, 34), (14, 34)]) self.assertEqual(len(sel.times), 3) # Number of timestamp (0.8, 1.0 and 1.2) - sel = self.test_obj['All'].get(tstart=5., tstop=-1) # tstart out of range - self.assertEqual(len(sel.ids), 0) - sel = self.test_obj['All'].get(tstart=3., tstop=3.) - self.assertEqual(len(sel.ids), 100) - + with self.assertRaises(RuntimeError): self.test_obj['All'].get(tstart=5.) # tstart out of range + with self.assertRaises(RuntimeError): self.test_obj['All'].get(tstart=3., tstop=3.) # tstart should be < tstop if __name__ == '__main__': unittest.main() From 3c2aa2cae84960b12e3ca08c777957c480090458 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 22 Jun 2020 20:07:48 +0200 Subject: [PATCH 06/35] clang-format --- python/bindings.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/python/bindings.cpp b/python/bindings.cpp index 22d40126..b97c96c4 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -304,13 +304,15 @@ py::class_ bindStorageClass(py::module& m, const char* clsName, const c } } // unnamed namespace -namespace pybind11 { namespace detail { - template - struct type_caster> : optional_caster> {}; +namespace pybind11 { +namespace detail { +template +struct type_caster>: optional_caster> {}; - template<> struct type_caster - : public void_caster {}; -}} +template <> +struct type_caster: public void_caster {}; +} // namespace detail +} // namespace pybind11 template void bindReportReader(py::module& m, const std::string& prefix) { From 5057ff946132d8c45cbdc8d21f934cd717a92def Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 23 Jun 2020 13:21:47 +0200 Subject: [PATCH 07/35] Throw SonataError and uint32_t => ElementId --- include/bbp/sonata/report_reader.h | 5 +++-- python/tests/test.py | 4 ++-- src/report_reader.cpp | 21 +++++++++++---------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index 50ea2368..8714538c 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -16,7 +16,7 @@ namespace H5 = HighFive; namespace bbp { namespace sonata { -// KeyType will be NodeID for somas report and pair for elements report +// KeyType will be NodeID for somas report and pair for elements report template struct SONATA_API DataFrame { using DataType = std::vector; @@ -127,8 +127,9 @@ class SONATA_API ReportReader mutable std::map populations_; }; +using ElementId = uint32_t; using SomaReportReader = ReportReader; -using ElementReportReader = ReportReader>; +using ElementReportReader = ReportReader>; } // namespace sonata } // namespace bbp diff --git a/python/tests/test.py b/python/tests/test.py index 49de4028..0ebe94e8 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -308,8 +308,8 @@ def test_get_reports_from_population(self): self.assertEqual(keys, [(13, 30), (13, 30), (13, 31), (13, 31), (13, 32), (14, 32), (14, 33), (14, 33), (14, 34), (14, 34)]) self.assertEqual(len(sel.times), 3) # Number of timestamp (0.8, 1.0 and 1.2) - with self.assertRaises(RuntimeError): self.test_obj['All'].get(tstart=5.) # tstart out of range - with self.assertRaises(RuntimeError): self.test_obj['All'].get(tstart=3., tstop=3.) # tstart should be < tstop + with self.assertRaises(SonataError): self.test_obj['All'].get(tstart=5.) # tstart out of range + with self.assertRaises(SonataError): self.test_obj['All'].get(tstart=3., tstop=3.) # tstart should be < tstop if __name__ == '__main__': unittest.main() diff --git a/src/report_reader.cpp b/src/report_reader.cpp index f8f73581..1d6743f1 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -14,6 +14,7 @@ HIGHFIVE_REGISTER_TYPE(bbp::sonata::SpikeReader::Population::Sorting, create_enu namespace { +using bbp::sonata::ElementId; using bbp::sonata::NodeID; using bbp::sonata::Selection; using bbp::sonata::Spike; @@ -75,15 +76,15 @@ void filterTimestampSorted(Spikes& spikes, double tstart, double tstop) { } template -T make_key(NodeID node_id, uint32_t element_id); +T make_key(NodeID node_id, ElementId element_id); template <> -NodeID make_key(NodeID node_id, uint32_t /* element_id */) { +NodeID make_key(NodeID node_id, ElementId /* element_id */) { return node_id; } template <> -std::pair make_key(NodeID node_id, uint32_t element_id) { +std::pair make_key(NodeID node_id, ElementId element_id) { return {node_id, element_id}; } @@ -142,7 +143,7 @@ SpikeReader::Population::Population(const std::string& filename, pop.getDataSet("timestamps").read(timestamps); if (node_ids.size() != timestamps.size()) { - throw std::runtime_error( + throw SonataError( "In spikes file, 'node_ids' and 'timestamps' does not have the same size."); } @@ -268,7 +269,7 @@ std::pair ReportReader::Population::getIndex(const nonstd::op double stop = tstop.value_or(tstop_); if (start < 0 - EPSILON || stop < 0 - EPSILON) { - throw std::runtime_error("Times cannot be negative"); + throw SonataError("Times cannot be negative"); } auto it_start = std::find_if(times_index_.cbegin(), times_index_.cend(), @@ -276,7 +277,7 @@ std::pair ReportReader::Population::getIndex(const nonstd::op return start < v.second + EPSILON; }); if (it_start == times_index_.end()) { - throw std::runtime_error("tstart is after the end of the range"); + throw SonataError("tstart is after the end of the range"); } indexes.first = it_start->first; @@ -285,7 +286,7 @@ std::pair ReportReader::Population::getIndex(const nonstd::op return stop > v.second - EPSILON; }); if (it_stop == times_index_.rend()) { - throw std::runtime_error("tstop is before the beginning of the range"); + throw SonataError("tstop is before the beginning of the range"); } indexes.second = it_stop->first; @@ -303,7 +304,7 @@ DataFrame ReportReader::Population::get(const nonstd::optional& std::tie(index_start, index_stop) = getIndex(tstart, tstop); if (index_start >= index_stop) { - throw std::runtime_error("tstart should be < to tstop"); + throw SonataError("tstart should be < to tstop"); } for (size_t i = index_start; i <= index_stop; ++i) { @@ -355,7 +356,7 @@ DataFrame ReportReader::Population::get(const nonstd::optional& ++timer_index; } - std::vector element_ids; + std::vector element_ids; pop_group_.getGroup("mapping") .getDataSet("element_ids") .select({it->second.first}, {it->second.second - it->second.first}) @@ -368,7 +369,7 @@ DataFrame ReportReader::Population::get(const nonstd::optional& } template class ReportReader; -template class ReportReader>; +template class ReportReader>; } // namespace sonata } // namespace bbp From c20e5ade868b4236adaa6232ce267f70aaaab9b6 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Jun 2020 00:43:11 +0200 Subject: [PATCH 08/35] ElementId => ElementID --- include/bbp/sonata/report_reader.h | 4 ++-- src/report_reader.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index 8714538c..135c9d7e 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -127,9 +127,9 @@ class SONATA_API ReportReader mutable std::map populations_; }; -using ElementId = uint32_t; +using ElementID = uint32_t; using SomaReportReader = ReportReader; -using ElementReportReader = ReportReader>; +using ElementReportReader = ReportReader>; } // namespace sonata } // namespace bbp diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 1d6743f1..33051d42 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -14,7 +14,7 @@ HIGHFIVE_REGISTER_TYPE(bbp::sonata::SpikeReader::Population::Sorting, create_enu namespace { -using bbp::sonata::ElementId; +using bbp::sonata::ElementID; using bbp::sonata::NodeID; using bbp::sonata::Selection; using bbp::sonata::Spike; @@ -76,15 +76,15 @@ void filterTimestampSorted(Spikes& spikes, double tstart, double tstop) { } template -T make_key(NodeID node_id, ElementId element_id); +T make_key(NodeID node_id, ElementID element_id); template <> -NodeID make_key(NodeID node_id, ElementId /* element_id */) { +NodeID make_key(NodeID node_id, ElementID /* element_id */) { return node_id; } template <> -std::pair make_key(NodeID node_id, ElementId element_id) { +std::pair make_key(NodeID node_id, ElementID element_id) { return {node_id, element_id}; } @@ -356,7 +356,7 @@ DataFrame ReportReader::Population::get(const nonstd::optional& ++timer_index; } - std::vector element_ids; + std::vector element_ids; pop_group_.getGroup("mapping") .getDataSet("element_ids") .select({it->second.first}, {it->second.second - it->second.first}) @@ -369,7 +369,7 @@ DataFrame ReportReader::Population::get(const nonstd::optional& } template class ReportReader; -template class ReportReader>; +template class ReportReader>; } // namespace sonata } // namespace bbp From 037ad05df5865dad2ab139c5d954d383897caca9 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Jun 2020 10:09:03 +0200 Subject: [PATCH 09/35] Add get_nodes_ids --- include/bbp/sonata/report_reader.h | 4 +++- python/bindings.cpp | 3 +++ python/tests/test.py | 3 +++ src/report_reader.cpp | 12 ++++++++---- tests/test_report_reader.cpp | 2 ++ 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index 135c9d7e..37ff7925 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -16,7 +16,7 @@ namespace H5 = HighFive; namespace bbp { namespace sonata { -// KeyType will be NodeID for somas report and pair for elements report +// KeyType will be NodeID for somas report and pair for elements report template struct SONATA_API DataFrame { using DataType = std::vector; @@ -94,6 +94,7 @@ class SONATA_API ReportReader std::string getTimeUnits() const; std::string getDataUnits() const; bool getSorted() const; + std::vector getNodesIds() const; DataFrame get(const nonstd::optional& nodes_ids = nonstd::nullopt, const nonstd::optional& _tstart = nonstd::nullopt, @@ -105,6 +106,7 @@ class SONATA_API ReportReader std::vector>> nodes_pointers_; H5::Group pop_group_; + std::vector nodes_ids_; double tstart_, tstop_, tstep_; std::vector> times_index_; std::string time_units_; diff --git a/python/bindings.cpp b/python/bindings.cpp index b97c96c4..8b3123bf 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -331,6 +331,9 @@ void bindReportReader(py::module& m, const std::string& prefix) { "node_ids"_a = nonstd::nullopt, "tstart"_a = nonstd::nullopt, "tstop"_a = nonstd::nullopt) + .def("get_nodes_ids", + &ReportType::Population::getNodesIds, + "Return the list of nodes ids for this population") .def_property_readonly("sorted", &ReportType::Population::getSorted, "Return if data are sorted") diff --git a/python/tests/test.py b/python/tests/test.py index 0ebe94e8..c9ef0b1d 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -294,6 +294,9 @@ def test_get_population(self): def test_get_inexistant_population(self): self.assertRaises(RuntimeError, self.test_obj.__getitem__, 'foobar') + def test_get_nodes_ids(self): + self.assertEqual(self.test_obj['All'].get_nodes_ids(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) + def test_get_reports_from_population(self): self.assertEqual(self.test_obj['All'].times, (0., 4., 0.2)) self.assertEqual(self.test_obj['All'].time_units, 'ms') diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 33051d42..327b1fea 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -207,14 +207,13 @@ ReportReader::Population::Population(const H5::File& file, const std::string& : pop_group_(file.getGroup(std::string("/report/") + populationName)) { { auto mapping_group = pop_group_.getGroup("mapping"); - std::vector node_ids; - mapping_group.getDataSet("node_ids").read(node_ids); + mapping_group.getDataSet("node_ids").read(nodes_ids_); std::vector index_pointers; mapping_group.getDataSet("index_pointers").read(index_pointers); - for (size_t i = 0; i < node_ids.size(); ++i) { - nodes_pointers_.emplace_back(node_ids[i], + for (size_t i = 0; i < nodes_ids_.size(); ++i) { + nodes_pointers_.emplace_back(nodes_ids_[i], std::make_pair(index_pointers[i], index_pointers[i + 1])); } @@ -261,6 +260,11 @@ bool ReportReader::Population::getSorted() const { return nodes_ids_sorted_; } +template +std::vector ReportReader::Population::getNodesIds() const { + return nodes_ids_; +} + template std::pair ReportReader::Population::getIndex(const nonstd::optional& tstart, const nonstd::optional& tstop) const { std::pair indexes; diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 7f0cd707..1d14bd44 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -71,6 +71,8 @@ TEST_CASE("SomaReportReader", "[base]") { REQUIRE(pop.getSorted()); + REQUIRE(pop.getNodeIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}); + auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); REQUIRE(data.ids == DataFrame::DataType{{3, 4}}); testTimes(data.times, 0.2, 0.1, 4); From 9a22658569467c77a6aef710b6b6d98775e5aa5d Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Jun 2020 10:18:51 +0200 Subject: [PATCH 10/35] clang format --- tests/test_report_reader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 1d14bd44..68b6b863 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -71,7 +71,8 @@ TEST_CASE("SomaReportReader", "[base]") { REQUIRE(pop.getSorted()); - REQUIRE(pop.getNodeIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}); + REQUIRE(pop.getNodeIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}); auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); REQUIRE(data.ids == DataFrame::DataType{{3, 4}}); From 06b234286b5babcc58f90a5da10e5d9c30583289 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Jun 2020 10:22:34 +0200 Subject: [PATCH 11/35] typo in c++ tests --- tests/test_report_reader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 68b6b863..c4436944 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -71,7 +71,7 @@ TEST_CASE("SomaReportReader", "[base]") { REQUIRE(pop.getSorted()); - REQUIRE(pop.getNodeIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + REQUIRE(pop.getNodesIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}); auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); From 8f9c42111d044a0f8539e53748c88422744cdc18 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Jun 2020 10:39:32 +0200 Subject: [PATCH 12/35] Use optional in SpikesReader --- include/bbp/sonata/report_reader.h | 6 +++--- src/report_reader.cpp | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index 37ff7925..cc908260 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -51,9 +51,9 @@ class SONATA_API SpikeReader by_time = 2, }; - Spikes get(const Selection& node_ids = Selection({}), - double tstart = -1, - double tstop = -1) const; + Spikes get(const nonstd::optional& node_ids = nonstd::nullopt, + const nonstd::optional& tstart = nonstd::nullopt, + const nonstd::optional& tstop = nonstd::nullopt) const; Sorting getSorting() const; private: diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 327b1fea..f7f13b28 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -109,18 +109,25 @@ auto SpikeReader::openPopulation(const std::string& populationName) const -> con return populations_.at(populationName); } -Spikes SpikeReader::Population::get(const Selection& node_ids, double tstart, double tstop) const { - tstart = tstart < 0 ? tstart_ : tstart; - tstop = tstop < 0 ? tstop_ : tstop; - if (tstart > tstop_ + EPSILON || tstop < tstart_ - EPSILON || tstop < tstart) { - return Spikes{}; +Spikes SpikeReader::Population::get(const nonstd::optional& node_ids, + const nonstd::optional& tstart, + const nonstd::optional& tstop) const { + double start = tstart.value_or(tstart_); + double stop = tstop.value_or(tstop_); + + if (start < 0 - EPSILON || stop < 0 - EPSILON) { + throw SonataError("Times cannot be negative"); + } + + if (start >= stop) { + throw SonataError("tstart should be < to tstop"); } auto spikes = spikes_; - filterTimestamp(spikes, tstart, tstop); + filterTimestamp(spikes, start, stop); - if (!node_ids.empty()) { - filterNode(spikes, node_ids); + if (node_ids and !node_ids->empty()) { + filterNode(spikes, node_ids.value()); } return spikes; From 036f71eaea055f49deb2385805edd01320b68918 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Jun 2020 11:45:31 +0200 Subject: [PATCH 13/35] Add limits tests for ElementReportReader --- tests/test_report_reader.cpp | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index c4436944..73f097b6 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -33,8 +33,6 @@ TEST_CASE("SpikeReader", "[base]") { TEST_CASE("SomaReportReader limits", "[base]") { const SomaReportReader reader("./data/somas.h5"); - REQUIRE(reader.getPopulationsNames() == std::vector{"All", "soma1", "soma2"}); - auto pop = reader.openPopulation("All"); // ids out of range @@ -50,7 +48,7 @@ TEST_CASE("SomaReportReader limits", "[base]") { REQUIRE_THROWS(pop.get(Selection({{1, 2}}), 100., 101.)); // Inverted times - // REQUIRE_THROWS(pop.get(Selection({{1, 2}}), 0.2, 0.1)); + REQUIRE_THROWS(pop.get(Selection({{1, 2}}), 0.2, 0.1)); // Negatives times REQUIRE_THROWS(pop.get(Selection({{1, 2}}), -1., -2.)); @@ -81,6 +79,30 @@ TEST_CASE("SomaReportReader", "[base]") { {{3.2f, 4.2f}, {3.3f, 4.3f}, {3.4f, 4.4f}, {3.5f, 4.5f}}}); } +TEST_CASE("ElementReportReader limits", "[base]") { + const ElementReportReader reader("./data/elements.h5"); + + auto pop = reader.openPopulation("All"); + + // ids out of range + // REQUIRE_THROWS(pop.get(Selection({{100, 101}}))); + + // Inverted id + REQUIRE_THROWS(pop.get(Selection({{2, 1}}))); + + // Negative ids + REQUIRE_THROWS(pop.get(Selection({{-1, 1}}))); + + // Times out of range + REQUIRE_THROWS(pop.get(Selection({{1, 2}}), 100., 101.)); + + // Inverted times + REQUIRE_THROWS(pop.get(Selection({{1, 2}}), 0.2, 0.1)); + + // Negatives times + REQUIRE_THROWS(pop.get(Selection({{1, 2}}), -1., -2.)); +} + TEST_CASE("ElementReportReader", "[base]") { const ElementReportReader reader("./data/elements.h5"); @@ -97,6 +119,9 @@ TEST_CASE("ElementReportReader", "[base]") { REQUIRE(pop.getSorted()); + REQUIRE(pop.getNodesIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}); + auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); REQUIRE(data.ids == DataFrame>::DataType{ From d276dbf8e4d64500aeb1eaa4014020cc6b15d7db Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Jun 2020 16:41:48 +0200 Subject: [PATCH 14/35] Simplify bindings of SpikeReader with optional --- python/bindings.cpp | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/python/bindings.cpp b/python/bindings.cpp index 8b3123bf..81718e80 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -497,29 +497,12 @@ PYBIND11_MODULE(_libsonata, m) { bindStorageClass(m, "EdgeStorage", "EdgePopulation"); py::class_(m, "SpikePopulation", "A population inside a SpikeReader") - .def( - "get", - [](const SpikeReader::Population& self) { return self.get(); }, - "Return all spikes") - .def( - "get", - [](const SpikeReader::Population& self, double tstart, double tstop) { - return self.get(Selection({}), tstart, tstop); - }, - "Return spikes between 'tstart' and 'tstop'", - "tstart"_a, - "tstop"_a) - .def( - "get", - [](const SpikeReader::Population& self, Selection sel) { return self.get(sel); }, - "Return spikes with all those node_ids", - "node_ids"_a) .def("get", &SpikeReader::Population::get, "Return spikes with all those node_ids between 'tstart' and 'tstop'", - "node_ids"_a, - "tstart"_a, - "tstop"_a) + "node_ids"_a = nonstd::nullopt, + "tstart"_a = nonstd::nullopt, + "tstop"_a = nonstd::nullopt) .def_property_readonly( "sorting", [](const SpikeReader::Population& self) { From e7fdeaf0377a34763471bdc3c3afba64ad6de852 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Jun 2020 17:35:54 +0200 Subject: [PATCH 15/35] Fix tests --- python/tests/test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tests/test.py b/python/tests/test.py index c9ef0b1d..856fa809 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -242,8 +242,8 @@ def test_get_inexistant_population(self): def test_get_spikes_from_population(self): self.assertEqual(self.test_obj['All'].get(), [(5, 0.1), (2, 0.2), (3, 0.3), (2, 0.7), (3, 1.3)]) - self.assertEqual(self.test_obj['All'].get(0.2, 1.0), [(2, 0.2), (3, 0.3), (2, 0.7)]) - self.assertEqual(self.test_obj['spikes2'].get(0.2, 1.0), [(3, 0.3), (2, 0.2), (2, 0.7)]) + self.assertEqual(self.test_obj['All'].get(tstart=0.2, tstop=1.0), [(2, 0.2), (3, 0.3), (2, 0.7)]) + self.assertEqual(self.test_obj['spikes2'].get(tstart=0.2, tstop=1.0), [(3, 0.3), (2, 0.2), (2, 0.7)]) self.assertEqual(self.test_obj['spikes1'].get((3,)), [(3, 0.3), (3, 1.3)]) self.assertEqual(self.test_obj['spikes2'].get((3,)), [(3, 0.3), (3, 1.3)]) self.assertEqual(self.test_obj['spikes2'].get((10,)), []) From 72b5516685258f3380cf0e58234296b115be8426 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 1 Jul 2020 19:06:09 +0200 Subject: [PATCH 16/35] Minor fix and testing data in python --- include/bbp/sonata/report_reader.h | 4 ++-- python/bindings.cpp | 4 ++-- python/tests/test.py | 5 +++-- src/report_reader.cpp | 12 ++++++------ tests/test_report_reader.cpp | 4 ++-- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index cc908260..fef2b230 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -94,9 +94,9 @@ class SONATA_API ReportReader std::string getTimeUnits() const; std::string getDataUnits() const; bool getSorted() const; - std::vector getNodesIds() const; + std::vector getNodeIds() const; - DataFrame get(const nonstd::optional& nodes_ids = nonstd::nullopt, + DataFrame get(const nonstd::optional& node_ids = nonstd::nullopt, const nonstd::optional& _tstart = nonstd::nullopt, const nonstd::optional& _tstop = nonstd::nullopt) const; diff --git a/python/bindings.cpp b/python/bindings.cpp index 81718e80..98efe9a6 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -331,8 +331,8 @@ void bindReportReader(py::module& m, const std::string& prefix) { "node_ids"_a = nonstd::nullopt, "tstart"_a = nonstd::nullopt, "tstop"_a = nonstd::nullopt) - .def("get_nodes_ids", - &ReportType::Population::getNodesIds, + .def("get_node_ids", + &ReportType::Population::getNodeIds, "Return the list of nodes ids for this population") .def_property_readonly("sorted", &ReportType::Population::getSorted, diff --git a/python/tests/test.py b/python/tests/test.py index 856fa809..a85581e1 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -294,8 +294,8 @@ def test_get_population(self): def test_get_inexistant_population(self): self.assertRaises(RuntimeError, self.test_obj.__getitem__, 'foobar') - def test_get_nodes_ids(self): - self.assertEqual(self.test_obj['All'].get_nodes_ids(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) + def test_get_node_ids(self): + self.assertEqual(self.test_obj['All'].get_node_ids(), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) def test_get_reports_from_population(self): self.assertEqual(self.test_obj['All'].times, (0., 4., 0.2)) @@ -313,6 +313,7 @@ def test_get_reports_from_population(self): self.assertEqual(len(sel.times), 3) # Number of timestamp (0.8, 1.0 and 1.2) with self.assertRaises(SonataError): self.test_obj['All'].get(tstart=5.) # tstart out of range with self.assertRaises(SonataError): self.test_obj['All'].get(tstart=3., tstop=3.) # tstart should be < tstop + np.testing.assert_allclose(np.array(self.test_obj['All'].get(node_ids=[3, 4], tstart=0.2, tstop=0.4).data[0]), [11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9], 1e-6, 0) if __name__ == '__main__': unittest.main() diff --git a/src/report_reader.cpp b/src/report_reader.cpp index f7f13b28..927d41bb 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -112,8 +112,8 @@ auto SpikeReader::openPopulation(const std::string& populationName) const -> con Spikes SpikeReader::Population::get(const nonstd::optional& node_ids, const nonstd::optional& tstart, const nonstd::optional& tstop) const { - double start = tstart.value_or(tstart_); - double stop = tstop.value_or(tstop_); + const double start = tstart.value_or(tstart_); + const double stop = tstop.value_or(tstop_); if (start < 0 - EPSILON || stop < 0 - EPSILON) { throw SonataError("Times cannot be negative"); @@ -268,7 +268,7 @@ bool ReportReader::Population::getSorted() const { } template -std::vector ReportReader::Population::getNodesIds() const { +std::vector ReportReader::Population::getNodeIds() const { return nodes_ids_; } @@ -325,7 +325,7 @@ DataFrame ReportReader::Population::get(const nonstd::optional& // Simplify selection // We should remove duplicates // And when we can work with ranges let's sort them - // auto nodes_ids_ = Selection::fromValues(nodes_ids.flatten().sort()); + // auto nodes_ids_ = Selection::fromValues(node_ids.flatten().sort()); Selection::Values node_ids; if (!selection || selection->empty()) { // Take all nodes in this case @@ -340,8 +340,8 @@ DataFrame ReportReader::Population::get(const nonstd::optional& } data_frame.data.resize(index_stop - index_start + 1); - // FIXME: It will be good to do it for ranges but if nodes_ids are not sorted it is not easy - // TODO: specialized this function for sorted nodes_ids? + // FIXME: It will be good to do it for ranges but if node_ids are not sorted it is not easy + // TODO: specialized this function for sorted node_ids? for (const auto& node_id : node_ids) { auto it = std::find_if( nodes_pointers_.begin(), diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 73f097b6..a3af3dc2 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -69,7 +69,7 @@ TEST_CASE("SomaReportReader", "[base]") { REQUIRE(pop.getSorted()); - REQUIRE(pop.getNodesIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + REQUIRE(pop.getNodeIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}); auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); @@ -119,7 +119,7 @@ TEST_CASE("ElementReportReader", "[base]") { REQUIRE(pop.getSorted()); - REQUIRE(pop.getNodesIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + REQUIRE(pop.getNodeIds() == std::vector{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}); auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); From e6bb7dd5594f5dc1a3628f63183fb0ed2ac150b1 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 1 Jul 2020 20:06:45 +0200 Subject: [PATCH 17/35] Fix merge --- include/bbp/sonata/report_reader.h | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index 4bfcc2c8..0b1048f7 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -125,20 +125,14 @@ class SONATA_API ReportReader bool getSorted() const; std::vector getNodeIds() const; -<<<<<<< HEAD - DataFrame get(const nonstd::optional& node_ids = nonstd::nullopt, - const nonstd::optional& _tstart = nonstd::nullopt, - const nonstd::optional& _tstop = nonstd::nullopt) const; -======= /** * \param node_ids limit the report to the given selection. - * \param tstart return spikes occurring on or after tstart. tstart=-1 indicates no limit. - * \param tstop return spikes occurring on or before tstop. tstop=-1 indicates no limit. + * \param tstart return spikes occurring on or after tstart. tstart=None indicates no limit. + * \param tstop return spikes occurring on or before tstop. tstop=None indicates no limit. */ - DataFrame get(const Selection& nodes_ids = Selection({}), - double _tstart = -1, - double _tstop = -1) const; ->>>>>>> origin/master + DataFrame get(const nonstd::optional& node_ids = nonstd::nullopt, + const nonstd::optional& _tstart = nonstd::nullopt, + const nonstd::optional& _tstop = nonstd::nullopt) const; private: Population(const H5::File& file, const std::string& populationName); From 1f371f2f4127c4ffa2b2691b5d21977cb48c99af Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 6 Jul 2020 14:30:19 +0200 Subject: [PATCH 18/35] Fix fix merge --- include/bbp/sonata/report_reader.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index 0b1048f7..a7523c91 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -58,10 +58,6 @@ class SONATA_API SpikeReader const nonstd::optional& tstart = nonstd::nullopt, const nonstd::optional& tstop = nonstd::nullopt) const; - Spikes get(const Selection& node_ids = Selection({}), - double tstart = -1, - double tstop = -1) const; - /** * Return the way data are sorted ('none', 'by_id', 'by_time') */ From 87973725e78f63bb669e79dbeeb75ad728b89b4d Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 6 Jul 2020 14:45:41 +0200 Subject: [PATCH 19/35] fix docstring --- include/bbp/sonata/report_reader.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index a7523c91..b0fa8c31 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -127,8 +127,8 @@ class SONATA_API ReportReader * \param tstop return spikes occurring on or before tstop. tstop=None indicates no limit. */ DataFrame get(const nonstd::optional& node_ids = nonstd::nullopt, - const nonstd::optional& _tstart = nonstd::nullopt, - const nonstd::optional& _tstop = nonstd::nullopt) const; + const nonstd::optional& tstart = nonstd::nullopt, + const nonstd::optional& tstop = nonstd::nullopt) const; private: Population(const H5::File& file, const std::string& populationName); From d1ec4a99e97b8e68c599613bcba9370cb3da2352 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 6 Jul 2020 19:24:20 +0200 Subject: [PATCH 20/35] Regenerate them --- python/generated/docstrings.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/python/generated/docstrings.h b/python/generated/docstrings.h index 36552f34..f17ffa37 100644 --- a/python/generated/docstrings.h +++ b/python/generated/docstrings.h @@ -255,23 +255,27 @@ R"doc(Parameter ``node_ids``: limit the report to the given selection. Parameter ``tstart``: - return spikes occurring on or after tstart. tstart=-1 indicates no - limit. + return spikes occurring on or after tstart. tstart=None indicates + no limit. Parameter ``tstop``: - return spikes occurring on or before tstop. tstop=-1 indicates no - limit.)doc"; + return spikes occurring on or before tstop. tstop=None indicates + no limit.)doc"; static const char *__doc_bbp_sonata_ReportReader_Population_getDataUnits = R"doc(Return the unit of data.)doc"; static const char *__doc_bbp_sonata_ReportReader_Population_getIndex = R"doc()doc"; +static const char *__doc_bbp_sonata_ReportReader_Population_getNodeIds = R"doc()doc"; + static const char *__doc_bbp_sonata_ReportReader_Population_getSorted = R"doc(Return true if the data is sorted.)doc"; static const char *__doc_bbp_sonata_ReportReader_Population_getTimeUnits = R"doc(Return the unit of time)doc"; static const char *__doc_bbp_sonata_ReportReader_Population_getTimes = R"doc(Return (tstart, tstop, tstep) of the population)doc"; +static const char *__doc_bbp_sonata_ReportReader_Population_nodes_ids = R"doc()doc"; + static const char *__doc_bbp_sonata_ReportReader_Population_nodes_ids_sorted = R"doc()doc"; static const char *__doc_bbp_sonata_ReportReader_Population_nodes_pointers = R"doc()doc"; From e976447c06db760feef0cc0b9b69de2f0e6ed979 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 7 Jul 2020 14:40:59 +0200 Subject: [PATCH 21/35] Fix out of range node ids --- include/bbp/sonata/report_reader.h | 4 ++-- src/report_reader.cpp | 5 +++++ tests/test_report_reader.cpp | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index b0fa8c31..caf7384d 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -123,8 +123,8 @@ class SONATA_API ReportReader /** * \param node_ids limit the report to the given selection. - * \param tstart return spikes occurring on or after tstart. tstart=None indicates no limit. - * \param tstop return spikes occurring on or before tstop. tstop=None indicates no limit. + * \param tstart return spikes occurring on or after tstart. tstart=nonstd::nullopt indicates no limit. + * \param tstop return spikes occurring on or before tstop. tstop=nonstd::nullopt indicates no limit. */ DataFrame get(const nonstd::optional& node_ids = nonstd::nullopt, const nonstd::optional& tstart = nonstd::nullopt, diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 927d41bb..77b8d9b9 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -376,6 +376,11 @@ DataFrame ReportReader::Population::get(const nonstd::optional& data_frame.ids.push_back(make_key(node_id, element_ids[i])); } } + + if (data_frame.ids.empty()) { + throw SonataError("Given ids are out of range"); + } + return data_frame; } diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index a3af3dc2..11968739 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -36,7 +36,7 @@ TEST_CASE("SomaReportReader limits", "[base]") { auto pop = reader.openPopulation("All"); // ids out of range - // REQUIRE_THROWS(pop.get(Selection({{100, 101}}))); + REQUIRE_THROWS(pop.get(Selection({{100, 101}}))); // Inverted id REQUIRE_THROWS(pop.get(Selection({{2, 1}}))); @@ -85,7 +85,7 @@ TEST_CASE("ElementReportReader limits", "[base]") { auto pop = reader.openPopulation("All"); // ids out of range - // REQUIRE_THROWS(pop.get(Selection({{100, 101}}))); + REQUIRE_THROWS(pop.get(Selection({{100, 101}}))); // Inverted id REQUIRE_THROWS(pop.get(Selection({{2, 1}}))); From 860a09692ac71fb049693af82309b92cd9eb156e Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 8 Jul 2020 19:37:32 +0200 Subject: [PATCH 22/35] clang-format and docstring --- include/bbp/sonata/report_reader.h | 5 +++-- python/generated/docstrings.h | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index caf7384d..8c38073b 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -123,8 +123,9 @@ class SONATA_API ReportReader /** * \param node_ids limit the report to the given selection. - * \param tstart return spikes occurring on or after tstart. tstart=nonstd::nullopt indicates no limit. - * \param tstop return spikes occurring on or before tstop. tstop=nonstd::nullopt indicates no limit. + * \param tstart return spikes occurring on or after tstart. tstart=nonstd::nullopt + * indicates no limit. \param tstop return spikes occurring on or before tstop. + * tstop=nonstd::nullopt indicates no limit. */ DataFrame get(const nonstd::optional& node_ids = nonstd::nullopt, const nonstd::optional& tstart = nonstd::nullopt, diff --git a/python/generated/docstrings.h b/python/generated/docstrings.h index f17ffa37..26155616 100644 --- a/python/generated/docstrings.h +++ b/python/generated/docstrings.h @@ -255,12 +255,12 @@ R"doc(Parameter ``node_ids``: limit the report to the given selection. Parameter ``tstart``: - return spikes occurring on or after tstart. tstart=None indicates - no limit. + return spikes occurring on or after tstart. tstart=nonstd::nullopt + indicates no limit. Parameter ``tstop``: - return spikes occurring on or before tstop. tstop=None indicates - no limit.)doc"; + return spikes occurring on or before tstop. tstop=nonstd::nullopt + indicates no limit.)doc"; static const char *__doc_bbp_sonata_ReportReader_Population_getDataUnits = R"doc(Return the unit of data.)doc"; From 8ff79ce73601fbd5e26d45cec28052b894f0867a Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 8 Jul 2020 23:18:32 +0200 Subject: [PATCH 23/35] Allow tstart == tstop --- src/report_reader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 77b8d9b9..98c3d35e 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -119,8 +119,8 @@ Spikes SpikeReader::Population::get(const nonstd::optional& node_ids, throw SonataError("Times cannot be negative"); } - if (start >= stop) { - throw SonataError("tstart should be < to tstop"); + if (start > stop) { + throw SonataError("tstart should be <= to tstop"); } auto spikes = spikes_; From 80e3675c9aadbc22b8805729594c61f7dd8c8b5c Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 8 Jul 2020 23:30:01 +0200 Subject: [PATCH 24/35] Return empty dataframe if [] as input --- python/tests/test.py | 6 +++++- src/report_reader.cpp | 8 +++++--- tests/test_report_reader.cpp | 6 ++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/python/tests/test.py b/python/tests/test.py index a85581e1..d9f76252 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -310,9 +310,13 @@ def test_get_reports_from_population(self): keys.sort() self.assertEqual(keys, [(13, 30), (13, 30), (13, 31), (13, 31), (13, 32), (14, 32), (14, 33), (14, 33), (14, 34), (14, 34)]) + self.assertEqual(len(self.test_obj['All'].get(node_ids=[]).data), 0) + self.assertEqual(len(self.test_obj['All'].get(node_ids=[]).times), 0) + self.assertEqual(len(self.test_obj['All'].get(node_ids=[]).ids), 0) + self.assertEqual(len(sel.times), 3) # Number of timestamp (0.8, 1.0 and 1.2) with self.assertRaises(SonataError): self.test_obj['All'].get(tstart=5.) # tstart out of range - with self.assertRaises(SonataError): self.test_obj['All'].get(tstart=3., tstop=3.) # tstart should be < tstop + self.test_obj['All'].get(tstart=3., tstop=3.) # tstart should be < tstop np.testing.assert_allclose(np.array(self.test_obj['All'].get(node_ids=[3, 4], tstart=0.2, tstop=0.4).data[0]), [11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9], 1e-6, 0) if __name__ == '__main__': diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 98c3d35e..f5860d9a 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -314,8 +314,8 @@ DataFrame ReportReader::Population::get(const nonstd::optional& size_t index_stop = 0; std::tie(index_start, index_stop) = getIndex(tstart, tstop); - if (index_start >= index_stop) { - throw SonataError("tstart should be < to tstop"); + if (index_start > index_stop) { + throw SonataError("tstart should be <= to tstop"); } for (size_t i = index_start; i <= index_stop; ++i) { @@ -328,13 +328,15 @@ DataFrame ReportReader::Population::get(const nonstd::optional& // auto nodes_ids_ = Selection::fromValues(node_ids.flatten().sort()); Selection::Values node_ids; - if (!selection || selection->empty()) { // Take all nodes in this case + if (!selection) { // Take all nodes in this case std::transform(nodes_pointers_.begin(), nodes_pointers_.end(), std::back_inserter(node_ids), [](const std::pair>& node_pointer) { return node_pointer.first; }); + } else if (selection->empty()) { + return DataFrame{}; } else { node_ids = selection->flatten(); } diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 11968739..52bc0d18 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -28,6 +28,8 @@ TEST_CASE("SpikeReader", "[base]") { SpikeReader::Population::Sorting::by_id); REQUIRE(reader.openPopulation("spikes2").getSorting() == SpikeReader::Population::Sorting::none); + + REQUIRE(reader.openPopulation("All").get(Selection({{5, 6}}), 0.1, 0.1) == std::vector>{{5, 0.1}}); } TEST_CASE("SomaReportReader limits", "[base]") { @@ -131,4 +133,8 @@ TEST_CASE("ElementReportReader", "[base]") { std::vector>{ {{11.0f, 11.1f, 11.2f, 11.3f, 11.4f, 11.5f, 11.6f, 11.7f, 11.8f, 11.9f}, {21.0f, 21.1f, 21.2f, 21.3f, 21.4f, 21.5f, 21.6f, 21.7f, 21.8f, 21.9f}}}); + + // Select only one time + REQUIRE(pop.get(Selection({{1, 2}}), 0.6, 0.6).data == std::vector>{ + {{30.0f, 30.1f, 30.2f, 30.3f, 30.4f}}}); } From 273d22e77537eac073bc6febee64ccb38785fe45 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 9 Jul 2020 00:01:48 +0200 Subject: [PATCH 25/35] clang format --- tests/test_report_reader.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 52bc0d18..3882a0bc 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -29,7 +29,8 @@ TEST_CASE("SpikeReader", "[base]") { REQUIRE(reader.openPopulation("spikes2").getSorting() == SpikeReader::Population::Sorting::none); - REQUIRE(reader.openPopulation("All").get(Selection({{5, 6}}), 0.1, 0.1) == std::vector>{{5, 0.1}}); + REQUIRE(reader.openPopulation("All").get(Selection({{5, 6}}), 0.1, 0.1) == + std::vector>{{5, 0.1}}); } TEST_CASE("SomaReportReader limits", "[base]") { @@ -135,6 +136,6 @@ TEST_CASE("ElementReportReader", "[base]") { {21.0f, 21.1f, 21.2f, 21.3f, 21.4f, 21.5f, 21.6f, 21.7f, 21.8f, 21.9f}}}); // Select only one time - REQUIRE(pop.get(Selection({{1, 2}}), 0.6, 0.6).data == std::vector>{ - {{30.0f, 30.1f, 30.2f, 30.3f, 30.4f}}}); + REQUIRE(pop.get(Selection({{1, 2}}), 0.6, 0.6).data == + std::vector>{{{30.0f, 30.1f, 30.2f, 30.3f, 30.4f}}}); } From 9432da74f79063e3f7089aae5694a323a12637d3 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 9 Jul 2020 15:19:02 +0200 Subject: [PATCH 26/35] Blindfix of this --- src/report_reader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/report_reader.cpp b/src/report_reader.cpp index f5860d9a..5c575b3a 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -336,7 +336,7 @@ DataFrame ReportReader::Population::get(const nonstd::optional& return node_pointer.first; }); } else if (selection->empty()) { - return DataFrame{}; + return DataFrame{{}, {}, {}}; } else { node_ids = selection->flatten(); } From 5b949a655ea002c7e16dcf77ce8255fabcf2a8e5 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 9 Jul 2020 15:50:28 +0200 Subject: [PATCH 27/35] node_ids=[] return empty for Spikes too --- python/tests/test.py | 2 ++ src/report_reader.cpp | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/python/tests/test.py b/python/tests/test.py index d9f76252..fca0f5ad 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -254,6 +254,8 @@ def test_get_spikes_from_population(self): self.assertEqual(self.test_obj['spikes1'].sorting, "by_id") self.assertEqual(self.test_obj['spikes2'].sorting, "none") + self.assertEqual(len(self.test_obj['All'].get(node_ids=[])), 0) + class TestSomaReportPopulation(unittest.TestCase): def setUp(self): path = os.path.join(PATH, "somas.h5") diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 5c575b3a..8436142e 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -123,10 +123,14 @@ Spikes SpikeReader::Population::get(const nonstd::optional& node_ids, throw SonataError("tstart should be <= to tstop"); } + if (node_ids and node_ids->empty()) { + return Spikes{}; + } + auto spikes = spikes_; filterTimestamp(spikes, start, stop); - if (node_ids and !node_ids->empty()) { + if (node_ids) { filterNode(spikes, node_ids.value()); } From 58d348c6b6d7ce3141ea4051e8ecaaa81311ce91 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 9 Jul 2020 15:58:39 +0200 Subject: [PATCH 28/35] Put ElementID in common.h --- include/bbp/sonata/common.h | 1 + include/bbp/sonata/report_reader.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/include/bbp/sonata/common.h b/include/bbp/sonata/common.h index af1be923..61ce7b11 100644 --- a/include/bbp/sonata/common.h +++ b/include/bbp/sonata/common.h @@ -38,6 +38,7 @@ SONATA_API const std::string version(); using NodeID = uint64_t; using EdgeID = uint64_t; +using ElementID = uint32_t; class SONATA_API SonataError: public std::runtime_error { diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index 8c38073b..2af70ac6 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -163,7 +163,6 @@ class SONATA_API ReportReader mutable std::map populations_; }; -using ElementID = uint32_t; using SomaReportReader = ReportReader; using ElementReportReader = ReportReader>; From 30683089a8b4ba0a0f416bb5c8c0a4e0eba5134b Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 9 Jul 2020 16:11:50 +0200 Subject: [PATCH 29/35] constify --- src/report_reader.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 8436142e..1adeb329 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -21,8 +21,8 @@ using bbp::sonata::Spike; using bbp::sonata::Spikes; void filterNodeIDUnsorted(Spikes& spikes, const Selection& node_ids) { - auto values = node_ids.flatten(); - auto new_end = std::remove_if(spikes.begin(), spikes.end(), [&values](const Spike& spike) { + const auto values = node_ids.flatten(); + const auto new_end = std::remove_if(spikes.begin(), spikes.end(), [&values](const Spike& spike) { return std::find(values.cbegin(), values.cend(), spike.first) == values.cend(); }); spikes.erase(new_end, spikes.end()); @@ -31,13 +31,13 @@ void filterNodeIDUnsorted(Spikes& spikes, const Selection& node_ids) { void filterNodeIDSorted(Spikes& spikes, const Selection& node_ids) { Spikes _spikes; for (const auto& range : node_ids.ranges()) { - auto begin = std::lower_bound(spikes.begin(), + const auto begin = std::lower_bound(spikes.begin(), spikes.end(), std::make_pair(range.first, 0.), [](const Spike& spike1, const Spike& spike2) { return spike1.first < spike2.first; }); - auto end = std::upper_bound(spikes.begin(), + const auto end = std::upper_bound(spikes.begin(), spikes.end(), std::make_pair(range.second - 1, 0.), [](const Spike& spike1, const Spike& spike2) { @@ -59,14 +59,14 @@ void filterTimestampUnsorted(Spikes& spikes, double tstart, double tstop) { } void filterTimestampSorted(Spikes& spikes, double tstart, double tstop) { - auto end = std::upper_bound(spikes.begin(), + const auto end = std::upper_bound(spikes.begin(), spikes.end(), std::make_pair(0UL, tstop + EPSILON), [](const Spike& spike1, const Spike& spike2) { return spike1.second < spike2.second; }); spikes.erase(end, spikes.end()); - auto begin = std::lower_bound(spikes.begin(), + const auto begin = std::lower_bound(spikes.begin(), spikes.end(), std::make_pair(0UL, tstart - EPSILON), [](const Spike& spike1, const Spike& spike2) { @@ -144,8 +144,8 @@ SpikeReader::Population::Sorting SpikeReader::Population::getSorting() const { SpikeReader::Population::Population(const std::string& filename, const std::string& populationName) { H5::File file(filename, H5::File::ReadOnly); - auto pop_path = std::string("/spikes/") + populationName; - auto pop = file.getGroup(pop_path); + const auto pop_path = std::string("/spikes/") + populationName; + const auto pop = file.getGroup(pop_path); std::vector node_ids; pop.getDataSet("node_ids").read(node_ids); @@ -217,7 +217,7 @@ template ReportReader::Population::Population(const H5::File& file, const std::string& populationName) : pop_group_(file.getGroup(std::string("/report/") + populationName)) { { - auto mapping_group = pop_group_.getGroup("mapping"); + const auto mapping_group = pop_group_.getGroup("mapping"); mapping_group.getDataSet("node_ids").read(nodes_ids_); std::vector index_pointers; @@ -280,14 +280,14 @@ template std::pair ReportReader::Population::getIndex(const nonstd::optional& tstart, const nonstd::optional& tstop) const { std::pair indexes; - double start = tstart.value_or(tstart_); - double stop = tstop.value_or(tstop_); + const double start = tstart.value_or(tstart_); + const double stop = tstop.value_or(tstop_); if (start < 0 - EPSILON || stop < 0 - EPSILON) { throw SonataError("Times cannot be negative"); } - auto it_start = std::find_if(times_index_.cbegin(), times_index_.cend(), + const auto it_start = std::find_if(times_index_.cbegin(), times_index_.cend(), [&](const std::pair& v) { return start < v.second + EPSILON; }); @@ -296,7 +296,7 @@ std::pair ReportReader::Population::getIndex(const nonstd::op } indexes.first = it_start->first; - auto it_stop = std::find_if(times_index_.crbegin(), times_index_.crend(), + const auto it_stop = std::find_if(times_index_.crbegin(), times_index_.crend(), [&](const std::pair& v) { return stop > v.second - EPSILON; }); From f443d134d8d09176d768996c4e1c49591a8eca1e Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 9 Jul 2020 16:13:03 +0200 Subject: [PATCH 30/35] Empty instead of throw if id out of range --- src/report_reader.cpp | 68 +++++++++++++++++++----------------- tests/test_report_reader.cpp | 7 ++-- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 1adeb329..b43b1678 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -22,9 +22,10 @@ using bbp::sonata::Spikes; void filterNodeIDUnsorted(Spikes& spikes, const Selection& node_ids) { const auto values = node_ids.flatten(); - const auto new_end = std::remove_if(spikes.begin(), spikes.end(), [&values](const Spike& spike) { - return std::find(values.cbegin(), values.cend(), spike.first) == values.cend(); - }); + const auto new_end = + std::remove_if(spikes.begin(), spikes.end(), [&values](const Spike& spike) { + return std::find(values.cbegin(), values.cend(), spike.first) == values.cend(); + }); spikes.erase(new_end, spikes.end()); } @@ -32,17 +33,17 @@ void filterNodeIDSorted(Spikes& spikes, const Selection& node_ids) { Spikes _spikes; for (const auto& range : node_ids.ranges()) { const auto begin = std::lower_bound(spikes.begin(), - spikes.end(), - std::make_pair(range.first, 0.), - [](const Spike& spike1, const Spike& spike2) { - return spike1.first < spike2.first; - }); + spikes.end(), + std::make_pair(range.first, 0.), + [](const Spike& spike1, const Spike& spike2) { + return spike1.first < spike2.first; + }); const auto end = std::upper_bound(spikes.begin(), - spikes.end(), - std::make_pair(range.second - 1, 0.), - [](const Spike& spike1, const Spike& spike2) { - return spike1.first < spike2.first; - }); + spikes.end(), + std::make_pair(range.second - 1, 0.), + [](const Spike& spike1, const Spike& spike2) { + return spike1.first < spike2.first; + }); std::move(begin, end, std::back_inserter(_spikes)); spikes.erase(begin, end); // have to erase, because otherwise it is no more sorted @@ -60,18 +61,18 @@ void filterTimestampUnsorted(Spikes& spikes, double tstart, double tstop) { void filterTimestampSorted(Spikes& spikes, double tstart, double tstop) { const auto end = std::upper_bound(spikes.begin(), - spikes.end(), - std::make_pair(0UL, tstop + EPSILON), - [](const Spike& spike1, const Spike& spike2) { - return spike1.second < spike2.second; - }); + spikes.end(), + std::make_pair(0UL, tstop + EPSILON), + [](const Spike& spike1, const Spike& spike2) { + return spike1.second < spike2.second; + }); spikes.erase(end, spikes.end()); const auto begin = std::lower_bound(spikes.begin(), - spikes.end(), - std::make_pair(0UL, tstart - EPSILON), - [](const Spike& spike1, const Spike& spike2) { - return spike1.second < spike2.second; - }); + spikes.end(), + std::make_pair(0UL, tstart - EPSILON), + [](const Spike& spike1, const Spike& spike2) { + return spike1.second < spike2.second; + }); spikes.erase(spikes.begin(), begin); } @@ -287,19 +288,20 @@ std::pair ReportReader::Population::getIndex(const nonstd::op throw SonataError("Times cannot be negative"); } - const auto it_start = std::find_if(times_index_.cbegin(), times_index_.cend(), - [&](const std::pair& v) { - return start < v.second + EPSILON; - }); + const auto it_start = std::find_if(times_index_.cbegin(), + times_index_.cend(), + [&](const std::pair& v) { + return start < v.second + EPSILON; + }); if (it_start == times_index_.end()) { throw SonataError("tstart is after the end of the range"); } indexes.first = it_start->first; - const auto it_stop = std::find_if(times_index_.crbegin(), times_index_.crend(), - [&](const std::pair& v) { - return stop > v.second - EPSILON; - }); + const auto it_stop = + std::find_if(times_index_.crbegin(), + times_index_.crend(), + [&](const std::pair& v) { return stop > v.second - EPSILON; }); if (it_stop == times_index_.rend()) { throw SonataError("tstop is before the beginning of the range"); } @@ -383,8 +385,8 @@ DataFrame ReportReader::Population::get(const nonstd::optional& } } - if (data_frame.ids.empty()) { - throw SonataError("Given ids are out of range"); + if (data_frame.ids.empty()) { // At the end no data available (wrong node_ids?) + return DataFrame{{}, {}, {}}; } return data_frame; diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 3882a0bc..31d7d0fb 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -39,7 +39,7 @@ TEST_CASE("SomaReportReader limits", "[base]") { auto pop = reader.openPopulation("All"); // ids out of range - REQUIRE_THROWS(pop.get(Selection({{100, 101}}))); + REQUIRE(pop.get(Selection({{100, 101}})).ids == DataFrame::DataType{}); // Inverted id REQUIRE_THROWS(pop.get(Selection({{2, 1}}))); @@ -88,7 +88,8 @@ TEST_CASE("ElementReportReader limits", "[base]") { auto pop = reader.openPopulation("All"); // ids out of range - REQUIRE_THROWS(pop.get(Selection({{100, 101}}))); + REQUIRE(pop.get(Selection({{100, 101}})).ids == + DataFrame>::DataType{}); // Inverted id REQUIRE_THROWS(pop.get(Selection({{2, 1}}))); @@ -127,7 +128,7 @@ TEST_CASE("ElementReportReader", "[base]") { auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); REQUIRE(data.ids == - DataFrame>::DataType{ + DataFrame>::DataType{ {{3, 5}, {3, 5}, {3, 6}, {3, 6}, {3, 7}, {4, 7}, {4, 8}, {4, 8}, {4, 9}, {4, 9}}}); testTimes(data.times, 0.2, 0.2, 2); REQUIRE(data.data == From f0d9848eb3f522f3c036804add0080b004cf7395 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 10 Jul 2020 16:25:57 +0200 Subject: [PATCH 31/35] Fix test where tstart == tstop --- python/tests/test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tests/test.py b/python/tests/test.py index fca0f5ad..75eaa6fc 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -318,8 +318,8 @@ def test_get_reports_from_population(self): self.assertEqual(len(sel.times), 3) # Number of timestamp (0.8, 1.0 and 1.2) with self.assertRaises(SonataError): self.test_obj['All'].get(tstart=5.) # tstart out of range - self.test_obj['All'].get(tstart=3., tstop=3.) # tstart should be < tstop - np.testing.assert_allclose(np.array(self.test_obj['All'].get(node_ids=[3, 4], tstart=0.2, tstop=0.4).data[0]), [11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9], 1e-6, 0) + np.testing.assert_allclose(self.test_obj['All'].get(node_ids=[1, 2], tstart=3., tstop=3.).data[0], [150.0, 150.1, 150.2, 150.3, 150.4, 150.5, 150.6, 150.7, 150.8, 150.9]) # tstart should be <= tstop + np.testing.assert_allclose(self.test_obj['All'].get(node_ids=[3, 4], tstart=0.2, tstop=0.4).data[0], [11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9], 1e-6, 0) if __name__ == '__main__': unittest.main() From 982ed5c3cd9e7380c9154f3df8eaa7e78bfc1b7d Mon Sep 17 00:00:00 2001 From: Blanco Alonso Jorge Date: Tue, 14 Jul 2020 12:30:58 +0200 Subject: [PATCH 32/35] Rename get_population_names() --- include/bbp/sonata/report_reader.h | 4 ++-- python/bindings.cpp | 10 +++++----- python/generated/docstrings.h | 4 ++-- python/tests/test.py | 6 +++--- tests/test_report_reader.cpp | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index 2af70ac6..cb297284 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -82,7 +82,7 @@ class SONATA_API SpikeReader /** * Return a list of all population names. */ - std::vector getPopulationsNames() const; + std::vector getPopulationNames() const; const Population& openPopulation(const std::string& populationName) const; @@ -152,7 +152,7 @@ class SONATA_API ReportReader /** * Return a list of all population names. */ - std::vector getPopulationsNames() const; + std::vector getPopulationNames() const; const Population& openPopulation(const std::string& populationName) const; diff --git a/python/bindings.cpp b/python/bindings.cpp index 8f279c82..f5369826 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -338,8 +338,8 @@ void bindReportReader(py::module& m, const std::string& prefix) { DOC_REPORTREADER_POP(getDataUnits)); py::class_(m, (prefix + "ReportReader").c_str(), "Used to read somas files") .def(py::init()) - .def("get_populations_names", - &ReportType::getPopulationsNames, + .def("get_population_names", + &ReportType::getPopulationNames, "Get list of all populations") .def("__getitem__", &ReportType::openPopulation); } @@ -500,9 +500,9 @@ PYBIND11_MODULE(_libsonata, m) { DOC_SPIKEREADER_POP(getSorting)); py::class_(m, "SpikeReader", "Used to read spike files") .def(py::init()) - .def("get_populations_names", - &SpikeReader::getPopulationsNames, - DOC_SPIKEREADER(getPopulationsNames)) + .def("get_population_names", + &SpikeReader::getPopulationNames, + DOC_SPIKEREADER(getPopulationNames)) .def("__getitem__", &SpikeReader::openPopulation); bindReportReader(m, "Soma"); diff --git a/python/generated/docstrings.h b/python/generated/docstrings.h index 26155616..0b25fe02 100644 --- a/python/generated/docstrings.h +++ b/python/generated/docstrings.h @@ -296,7 +296,7 @@ static const char *__doc_bbp_sonata_ReportReader_ReportReader = R"doc()doc"; static const char *__doc_bbp_sonata_ReportReader_file = R"doc()doc"; -static const char *__doc_bbp_sonata_ReportReader_getPopulationsNames = R"doc(Return a list of all population names.)doc"; +static const char *__doc_bbp_sonata_ReportReader_getPopulationNames = R"doc(Return a list of all population names.)doc"; static const char *__doc_bbp_sonata_ReportReader_openPopulation = R"doc()doc"; @@ -365,7 +365,7 @@ static const char *__doc_bbp_sonata_SpikeReader_SpikeReader = R"doc()doc"; static const char *__doc_bbp_sonata_SpikeReader_filename = R"doc()doc"; -static const char *__doc_bbp_sonata_SpikeReader_getPopulationsNames = R"doc(Return a list of all population names.)doc"; +static const char *__doc_bbp_sonata_SpikeReader_getPopulationNames = R"doc(Return a list of all population names.)doc"; static const char *__doc_bbp_sonata_SpikeReader_openPopulation = R"doc()doc"; diff --git a/python/tests/test.py b/python/tests/test.py index 75eaa6fc..ec18e2ac 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -232,7 +232,7 @@ def setUp(self): self.test_obj = SpikeReader(path) def test_get_all_populations(self): - self.assertEqual(self.test_obj.get_populations_names(), ['All', 'spikes1', 'spikes2']) + self.assertEqual(self.test_obj.get_population_names(), ['All', 'spikes1', 'spikes2']) def test_get_population(self): self.assertTrue(isinstance(self.test_obj['spikes1'], SpikePopulation)) @@ -262,7 +262,7 @@ def setUp(self): self.test_obj = SomaReportReader(path) def test_get_all_population(self): - self.assertEqual(self.test_obj.get_populations_names(), ['All', 'soma1', 'soma2']) + self.assertEqual(self.test_obj.get_population_names(), ['All', 'soma1', 'soma2']) def test_get_population(self): self.assertTrue(isinstance(self.test_obj['All'], SomaReportPopulation)) @@ -288,7 +288,7 @@ def setUp(self): self.test_obj = ElementReportReader(path) def test_get_all_population(self): - self.assertEqual(self.test_obj.get_populations_names(), ['All', 'element1', 'element42']) + self.assertEqual(self.test_obj.get_population_names(), ['All', 'element1', 'element42']) def test_get_population(self): self.assertTrue(isinstance(self.test_obj['All'], ElementReportPopulation)) diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index 31d7d0fb..dde54722 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -14,7 +14,7 @@ void testTimes(const std::vector& vec, double start, double step, int si TEST_CASE("SpikeReader", "[base]") { const SpikeReader reader("./data/spikes.h5"); - REQUIRE(reader.getPopulationsNames() == std::vector{"All", "spikes1", "spikes2"}); + REQUIRE(reader.getPopulationNames() == std::vector{"All", "spikes1", "spikes2"}); REQUIRE(reader.openPopulation("All").get(Selection({{3, 4}})) == std::vector>{{3UL, 0.3}, {3UL, 1.3}}); @@ -60,7 +60,7 @@ TEST_CASE("SomaReportReader limits", "[base]") { TEST_CASE("SomaReportReader", "[base]") { const SomaReportReader reader("./data/somas.h5"); - REQUIRE(reader.getPopulationsNames() == std::vector{"All", "soma1", "soma2"}); + REQUIRE(reader.getPopulationNames() == std::vector{"All", "soma1", "soma2"}); auto pop = reader.openPopulation("All"); @@ -110,7 +110,7 @@ TEST_CASE("ElementReportReader limits", "[base]") { TEST_CASE("ElementReportReader", "[base]") { const ElementReportReader reader("./data/elements.h5"); - REQUIRE(reader.getPopulationsNames() == + REQUIRE(reader.getPopulationNames() == std::vector{"All", "element1", "element42"}); auto pop = reader.openPopulation("All"); From 2a336e7bcdba41b0d3edc0c7217c6576615b6531 Mon Sep 17 00:00:00 2001 From: Blanco Alonso Jorge Date: Tue, 14 Jul 2020 12:36:30 +0200 Subject: [PATCH 33/35] Clang format --- python/bindings.cpp | 5 +---- src/report_reader.cpp | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/python/bindings.cpp b/python/bindings.cpp index f5369826..68f48905 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -338,9 +338,7 @@ void bindReportReader(py::module& m, const std::string& prefix) { DOC_REPORTREADER_POP(getDataUnits)); py::class_(m, (prefix + "ReportReader").c_str(), "Used to read somas files") .def(py::init()) - .def("get_population_names", - &ReportType::getPopulationNames, - "Get list of all populations") + .def("get_population_names", &ReportType::getPopulationNames, "Get list of all populations") .def("__getitem__", &ReportType::openPopulation); } @@ -364,7 +362,6 @@ PYBIND11_MODULE(_libsonata, m) { "__bool__", [](const Selection& obj) { return !obj.empty(); }, "True if Selection is not empty") - .def("__eq__", &bbp::sonata::operator==, "Compare selection contents are equal") .def("__ne__", &bbp::sonata::operator!=, "Compare selection contents are not equal") .def("__or__", &bbp::sonata::operator|, "Union of selections") diff --git a/src/report_reader.cpp b/src/report_reader.cpp index b43b1678..9c883e2a 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -97,7 +97,7 @@ namespace sonata { SpikeReader::SpikeReader(const std::string& filename) : filename_(filename) {} -std::vector SpikeReader::getPopulationsNames() const { +std::vector SpikeReader::getPopulationNames() const { H5::File file(filename_, H5::File::ReadOnly); return file.getGroup("/spikes").listObjectNames(); } @@ -201,7 +201,7 @@ ReportReader::ReportReader(const std::string& filename) : file_(filename, H5::File::ReadOnly) {} template -std::vector ReportReader::getPopulationsNames() const { +std::vector ReportReader::getPopulationNames() const { return file_.getGroup("/report").listObjectNames(); } From 927f4c1bded987940e7f258eb72b329e469f20b0 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 14 Jul 2020 18:36:27 +0200 Subject: [PATCH 34/35] Expose Report data as Numpy (#99) We finally solve the problem of array data stored somewhere and we want to return only a wrapper numpy array. Co-authored-by: Fernando Pereira --- include/bbp/sonata/report_reader.h | 4 +- python/bindings.cpp | 32 +++++++++++++-- python/tests/test.py | 6 ++- src/report_reader.cpp | 62 ++++++++++++++++++++---------- tests/test_report_reader.cpp | 12 +++--- 5 files changed, 83 insertions(+), 33 deletions(-) diff --git a/include/bbp/sonata/report_reader.h b/include/bbp/sonata/report_reader.h index cb297284..e724a778 100644 --- a/include/bbp/sonata/report_reader.h +++ b/include/bbp/sonata/report_reader.h @@ -22,8 +22,8 @@ struct SONATA_API DataFrame { using DataType = std::vector; std::vector times; DataType ids; - // data[times][ids] - std::vector> data; + // data[times][ids], flattened. n_cols is ids.size() + std::vector data; }; using Spike = std::pair; diff --git a/python/bindings.cpp b/python/bindings.cpp index 68f48905..5acc9da5 100644 --- a/python/bindings.cpp +++ b/python/bindings.cpp @@ -52,6 +52,16 @@ py::array asArray(std::vector&& values) { } +// Return a new Numpy array with data owned by another python object +// This avoids copies, and enables correct reference counting for memory keep-alive +template +py::array managedMemoryArray(const DATA_T* data, const DIMS_T& dims, const OWNER_T& owner) { + const auto& tinfo = py::detail::get_type_info(typeid(OWNER_T)); + const auto& handle = py::detail::get_object_handle(&owner, tinfo); + return py::array(dims, data, handle); +} + + template py::object getAttribute(const Population& obj, const std::string& name, @@ -304,14 +314,30 @@ struct type_caster: public void_caster {}; } // namespace detail } // namespace pybind11 + template void bindReportReader(py::module& m, const std::string& prefix) { py::class_>(m, (prefix + "DataFrame").c_str(), - "Something easily convertible to pandas dataframe") + "A container of raw reporting data, compatible with Pandas") .def_readonly("ids", &DataFrame::ids) - .def_readonly("data", &DataFrame::data) - .def_readonly("times", &DataFrame::times); + + // .data and .time members are owned by this c++ object. We can't do std::move. + // To avoid copies we must declare the owner of the data is the current python + // object. Numpy will adjust owner reference count according to returned arrays + // clang-format off + .def_property_readonly("data", [](const DataFrame& dframe) { + std::array dims {0l, ssize_t(dframe.ids.size())}; + if (dims[1] > 0) { + dims[0] = dframe.data.size() / dims[1]; + } + return managedMemoryArray(dframe.data.data(), dims, dframe); + }) + // clang-format on + .def_property_readonly("times", [](DataFrame& dframe) { + return managedMemoryArray(dframe.times.data(), dframe.times.size(), dframe); + }); + py::class_(m, (prefix + "ReportPopulation").c_str(), "A population inside a ReportReader") diff --git a/python/tests/test.py b/python/tests/test.py index ec18e2ac..24f02219 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -301,6 +301,9 @@ def test_get_node_ids(self): def test_get_reports_from_population(self): self.assertEqual(self.test_obj['All'].times, (0., 4., 0.2)) + # check following calls succeed (no memory destroyed) + self.assertEqual(self.test_obj['All'].times, (0., 4., 0.2)) + self.assertEqual(self.test_obj['All'].time_units, 'ms') self.assertEqual(self.test_obj['All'].data_units, 'mV') self.assertTrue(self.test_obj['All'].sorted) @@ -317,7 +320,8 @@ def test_get_reports_from_population(self): self.assertEqual(len(self.test_obj['All'].get(node_ids=[]).ids), 0) self.assertEqual(len(sel.times), 3) # Number of timestamp (0.8, 1.0 and 1.2) - with self.assertRaises(SonataError): self.test_obj['All'].get(tstart=5.) # tstart out of range + with self.assertRaises(SonataError): + self.test_obj['All'].get(tstart=5.) # tstart out of range np.testing.assert_allclose(self.test_obj['All'].get(node_ids=[1, 2], tstart=3., tstop=3.).data[0], [150.0, 150.1, 150.2, 150.3, 150.4, 150.5, 150.6, 150.7, 150.8, 150.9]) # tstart should be <= tstop np.testing.assert_allclose(self.test_obj['All'].get(node_ids=[3, 4], tstart=0.2, tstop=0.4).data[0], [11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9], 1e-6, 0) diff --git a/src/report_reader.cpp b/src/report_reader.cpp index 9c883e2a..91794086 100644 --- a/src/report_reader.cpp +++ b/src/report_reader.cpp @@ -278,7 +278,8 @@ std::vector ReportReader::Population::getNodeIds() const { } template -std::pair ReportReader::Population::getIndex(const nonstd::optional& tstart, const nonstd::optional& tstop) const { +std::pair ReportReader::Population::getIndex( + const nonstd::optional& tstart, const nonstd::optional& tstop) const { std::pair indexes; const double start = tstart.value_or(tstart_); @@ -310,6 +311,7 @@ std::pair ReportReader::Population::getIndex(const nonstd::op return indexes; } + template DataFrame ReportReader::Population::get(const nonstd::optional& selection, const nonstd::optional& tstart, @@ -318,8 +320,8 @@ DataFrame ReportReader::Population::get(const nonstd::optional& size_t index_start = 0; size_t index_stop = 0; - std::tie(index_start, index_stop) = getIndex(tstart, tstop); + if (index_start > index_stop) { throw SonataError("tstart should be <= to tstop"); } @@ -347,11 +349,41 @@ DataFrame ReportReader::Population::get(const nonstd::optional& node_ids = selection->flatten(); } - data_frame.data.resize(index_stop - index_start + 1); + for (const auto& node_id : node_ids) { + const auto it = std::find_if( + nodes_pointers_.begin(), + nodes_pointers_.end(), + [&node_id](const std::pair>& node_pointer) { + return node_pointer.first == node_id; + }); + if (it == nodes_pointers_.end()) { + continue; + } + + std::vector element_ids; + pop_group_.getGroup("mapping") + .getDataSet("element_ids") + .select({it->second.first}, {it->second.second - it->second.first}) + .read(element_ids); + for (const auto& elem : element_ids) { + data_frame.ids.push_back(make_key(node_id, elem)); + } + } + if (data_frame.ids.empty()) { // At the end no data available (wrong node_ids?) + return DataFrame{{}, {}, {}}; + } + + // Fill .data member + + auto n_time_entries = index_stop - index_start + 1; + auto n_ids = data_frame.ids.size(); + data_frame.data.resize(n_time_entries * n_ids); + // FIXME: It will be good to do it for ranges but if node_ids are not sorted it is not easy // TODO: specialized this function for sorted node_ids? + int ids_index = 0; for (const auto& node_id : node_ids) { - auto it = std::find_if( + const auto it = std::find_if( nodes_pointers_.begin(), nodes_pointers_.end(), [&node_id](const std::pair>& node_pointer) { @@ -367,26 +399,16 @@ DataFrame ReportReader::Population::get(const nonstd::optional& .select({index_start, it->second.first}, {index_stop - index_start + 1, it->second.second - it->second.first}) .read(data); + int timer_index = 0; + for (const std::vector& datum : data) { - for (float d : datum) { - data_frame.data[timer_index].push_back(d); - } + std::copy(datum.data(), + datum.data() + datum.size(), + &data_frame.data[timer_index * n_ids + ids_index]); ++timer_index; } - - std::vector element_ids; - pop_group_.getGroup("mapping") - .getDataSet("element_ids") - .select({it->second.first}, {it->second.second - it->second.first}) - .read(element_ids); - for (size_t i = 0; i < element_ids.size(); ++i) { - data_frame.ids.push_back(make_key(node_id, element_ids[i])); - } - } - - if (data_frame.ids.empty()) { // At the end no data available (wrong node_ids?) - return DataFrame{{}, {}, {}}; + ids_index += data[0].size(); } return data_frame; diff --git a/tests/test_report_reader.cpp b/tests/test_report_reader.cpp index dde54722..74eb70a9 100644 --- a/tests/test_report_reader.cpp +++ b/tests/test_report_reader.cpp @@ -78,8 +78,7 @@ TEST_CASE("SomaReportReader", "[base]") { auto data = pop.get(Selection({{3, 5}}), 0.2, 0.5); REQUIRE(data.ids == DataFrame::DataType{{3, 4}}); testTimes(data.times, 0.2, 0.1, 4); - REQUIRE(data.data == std::vector>{ - {{3.2f, 4.2f}, {3.3f, 4.3f}, {3.4f, 4.4f}, {3.5f, 4.5f}}}); + REQUIRE(data.data == std::vector{3.2f, 4.2f, 3.3f, 4.3f, 3.4f, 4.4f, 3.5f, 4.5f}); } TEST_CASE("ElementReportReader limits", "[base]") { @@ -131,12 +130,11 @@ TEST_CASE("ElementReportReader", "[base]") { DataFrame>::DataType{ {{3, 5}, {3, 5}, {3, 6}, {3, 6}, {3, 7}, {4, 7}, {4, 8}, {4, 8}, {4, 9}, {4, 9}}}); testTimes(data.times, 0.2, 0.2, 2); - REQUIRE(data.data == - std::vector>{ - {{11.0f, 11.1f, 11.2f, 11.3f, 11.4f, 11.5f, 11.6f, 11.7f, 11.8f, 11.9f}, - {21.0f, 21.1f, 21.2f, 21.3f, 21.4f, 21.5f, 21.6f, 21.7f, 21.8f, 21.9f}}}); + REQUIRE(data.data == std::vector{11.0f, 11.1f, 11.2f, 11.3f, 11.4f, 11.5f, 11.6f, + 11.7f, 11.8f, 11.9f, 21.0f, 21.1f, 21.2f, 21.3f, + 21.4f, 21.5f, 21.6f, 21.7f, 21.8f, 21.9f}); // Select only one time REQUIRE(pop.get(Selection({{1, 2}}), 0.6, 0.6).data == - std::vector>{{{30.0f, 30.1f, 30.2f, 30.3f, 30.4f}}}); + std::vector{30.0f, 30.1f, 30.2f, 30.3f, 30.4f}); } From 68e6ada2ef60d76ce5f52474dfac4c78953ede82 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 15 Jul 2020 15:07:39 +0200 Subject: [PATCH 35/35] Add a test to be sure that data are not destroyed --- python/tests/test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/tests/test.py b/python/tests/test.py index 24f02219..496501ec 100644 --- a/python/tests/test.py +++ b/python/tests/test.py @@ -323,6 +323,8 @@ def test_get_reports_from_population(self): with self.assertRaises(SonataError): self.test_obj['All'].get(tstart=5.) # tstart out of range np.testing.assert_allclose(self.test_obj['All'].get(node_ids=[1, 2], tstart=3., tstop=3.).data[0], [150.0, 150.1, 150.2, 150.3, 150.4, 150.5, 150.6, 150.7, 150.8, 150.9]) # tstart should be <= tstop + # check following calls succeed (no memory destroyed) + np.testing.assert_allclose(self.test_obj['All'].get(node_ids=[1, 2], tstart=3., tstop=3.).data[0], [150.0, 150.1, 150.2, 150.3, 150.4, 150.5, 150.6, 150.7, 150.8, 150.9]) np.testing.assert_allclose(self.test_obj['All'].get(node_ids=[3, 4], tstart=0.2, tstop=0.4).data[0], [11.0, 11.1, 11.2, 11.3, 11.4, 11.5, 11.6, 11.7, 11.8, 11.9], 1e-6, 0) if __name__ == '__main__':