diff --git a/brain/python/CMakeLists.txt b/brain/python/CMakeLists.txt index 1516947..b174ce0 100644 --- a/brain/python/CMakeLists.txt +++ b/brain/python/CMakeLists.txt @@ -18,6 +18,7 @@ include(PythonDocstrings) set(BRAIN_PYTHON_SOURCES brain.cpp circuit.cpp + spikeReportReader.cpp synapses.cpp neuron/morphology.cpp ) @@ -29,7 +30,6 @@ list(APPEND BRAIN_PYTHON_SOURCES arrayHelpers.cpp helpers.cpp spikeReportWriter.cpp - spikeReportReader.cpp spikes.cpp submodules.cpp test.cpp diff --git a/brain/python/arrayHelpers.cpp b/brain/python/arrayHelpers.cpp index 810ec4d..bb0a967 100644 --- a/brain/python/arrayHelpers.cpp +++ b/brain/python/arrayHelpers.cpp @@ -41,17 +41,43 @@ namespace // Helper clasess for template meta-programming of create_array_from_vector template< typename T > struct NumpyArrayInfo; -#define DECLARE_ARRAY_INFO( T, NPY_TYPE, NUM_DIM, DIMENSIONS... ) \ +#define DECLARE_ARRAY_INFO( T, NPY_TYPE, NUM_DIM, /*DIMENSIONS*/... ) \ template<> struct NumpyArrayInfo< T > \ { \ - NumpyArrayInfo( const T*, const npy_intp size ) \ - : dims{ size, DIMENSIONS } \ + NumpyArrayInfo( const npy_intp size ) \ + : descr( PyArray_DescrFromType( NPY_TYPE )) \ + , dims{ size, __VA_ARGS__ } \ {} \ static const int ndims = NUM_DIM; \ - static const int type = NPY_TYPE; \ + PyArray_Descr* descr; \ npy_intp dims[ndims]; \ +}; + +PyArray_Descr* createDtype( const char* dtype ) +{ + bp::object format( dtype ); + PyArray_Descr* descr; + if( PyArray_DescrConverter( format.ptr(), &descr ) == -1 ) + { + PyErr_SetString( PyExc_RuntimeError, "Internal wrapping error in C++ to" + " numpy array conversion" ); + bp::throw_error_already_set(); + } + return descr; } +#define DECLARE_STRUCTURED_ARRAY_INFO( T, DTYPE_STR ) \ +template<> struct NumpyArrayInfo< T > \ +{ \ + NumpyArrayInfo( const npy_intp size ) \ + : descr( createDtype( DTYPE_STR )) \ + , dims{ size } \ + {} \ + static const int ndims = 1; \ + PyArray_Descr* descr; \ + npy_intp dims[1]; \ +}; + DECLARE_ARRAY_INFO( uint32_t, NPY_UINT, 1 ); DECLARE_ARRAY_INFO( int, NPY_INT, 1 ); DECLARE_ARRAY_INFO( size_t, NPY_LONG, 1 ); @@ -62,20 +88,14 @@ DECLARE_ARRAY_INFO( Vector3f, NPY_FLOAT, 2, 3 ); DECLARE_ARRAY_INFO( Vector4f, NPY_FLOAT, 2, 4 ); DECLARE_ARRAY_INFO( Quaternionf, NPY_FLOAT, 2, 4 ); DECLARE_ARRAY_INFO( Matrix4f, NPY_FLOAT, 3, 4, 4 ); +DECLARE_STRUCTURED_ARRAY_INFO( Spike, "f4, u4" ); // Functions for the boost::shared_ptr< std::vector< T >> to numpy converter -template< typename T > -PyObject* _createNumpyArray( const T* data, const size_t size, - const AbstractCustodianPtr& keeper ) +void _setBaseObject( PyObject* array, const AbstractCustodianPtr& keeper ) { - typedef NumpyArrayInfo< T > Info; - Info info( data, size ); - PyObject* array = PyArray_SimpleNewFromData( - Info::ndims, info.dims, Info::type, ( void* )data ); - - // Pointer sharing between the C++ shrared_ptr and the numpy array is only - // available for Numpy >= 1.7. + // Pointer sharing between the C++ shrared_ptr and the numpy array is + // only available for Numpy >= 1.7. // PyArray_SetBaseObject steels the reference. bp::object pykeeper( keeper ); Py_INCREF( pykeeper.ptr( )); @@ -87,10 +107,23 @@ PyObject* _createNumpyArray( const T* data, const size_t size, Py_DECREF( pykeeper.ptr( )); bp::throw_error_already_set(); } - return array; } template< typename T > +PyObject* _createNumpyArray( const T* data, const size_t size, + const AbstractCustodianPtr& keeper ) +{ + typedef NumpyArrayInfo< T > Info; + Info info( size ); + PyObject* array = PyArray_NewFromDescr( + &PyArray_Type, info.descr, Info::ndims, info.dims, nullptr, + ( void* )data, 0, nullptr); + _setBaseObject( array, keeper ); + return array; +}; + + +template< typename T > PyObject* _createNumpyArray( const std::vector< T >& vector, const AbstractCustodianPtr& keeper ) { @@ -172,6 +205,7 @@ void importArray() REGISTER_ARRAY_CONVERTER( Vector4f ); REGISTER_ARRAY_CONVERTER( Quaternionf ); REGISTER_ARRAY_CONVERTER( Matrix4f ); + REGISTER_ARRAY_CONVERTER( Spike ); bp::class_< AbstractCustodian, AbstractCustodianPtr >( "_Custodian" ); } diff --git a/brain/python/spikeReportReader.cpp b/brain/python/spikeReportReader.cpp index 5436224..07e70e1 100644 --- a/brain/python/spikeReportReader.cpp +++ b/brain/python/spikeReportReader.cpp @@ -19,6 +19,10 @@ #include +#include "arrayHelpers.h" +#include "docstrings.h" +#include "helpers.h" + #include #include @@ -33,19 +37,45 @@ SpikeReportReaderPtr _initURI( const std::string& uri ) { return SpikeReportReaderPtr( new SpikeReportReader( brion::URI( uri ))); } + +SpikeReportReaderPtr _initURIandGIDSet( const std::string& uri, + bp::object gids ) +{ + return SpikeReportReaderPtr( + new SpikeReportReader( brion::URI( uri ), gidsFromPython( gids ))); +} + +bp::object SpikeReportReader_getSpikes(SpikeReportReader& reader, + const float startTime, + const float endTime ) +{ + return toNumpy( reader.getSpikes( startTime, endTime )); +} } void export_SpikeReportReader() { +const auto selfarg = bp::arg( "self" ); + +// clang-format off bp::class_< SpikeReportReader, boost::noncopyable >( "SpikeReportReader", bp::no_init ) - .def( "__init__", bp::make_constructor( _initURI )) - .def( "close", &SpikeReportReader::close ) - .def( "getSpikes", - ( brion::Spikes (SpikeReportReader::* )( float, float )) - &SpikeReportReader::getSpikes ) - .def( "hasEnded", &SpikeReportReader::hasEnded ); + .def( "__init__", bp::make_constructor( _initURI ), + DOXY_FN( brain::SpikeReportReader::SpikeReportReader( const brion::URI& ))) + .def( "__init__", bp::make_constructor( _initURIandGIDSet ), + DOXY_FN( brain::SpikeReportReader::SpikeReportReader( const brion::URI&, const GIDSet& ))) + .def( "close", &SpikeReportReader::close, + DOXY_FN( brain::SpikeReportReader::close )) + .def( "get_spikes", SpikeReportReader_getSpikes, + ( selfarg, bp::arg( "start_time" ), bp::arg( "stop_time" )), + DOXY_FN( brain::SpikeReportReader::getSpikes )) + .add_property( "end_time", &SpikeReportReader::getEndTime, + DOXY_FN( brain::SpikeReportReader::getEndTime )) + .add_property( "has_ended", &SpikeReportReader::hasEnded, + DOXY_FN( brain::SpikeReportReader::hasEnded )) + ; +// clang-format on } } diff --git a/brain/python/test.cpp b/brain/python/test.cpp index 7fdcaef..978403a 100644 --- a/brain/python/test.cpp +++ b/brain/python/test.cpp @@ -35,11 +35,12 @@ void export_test() for( const auto& config : bbp::test::getBlueconfigs( )) configs.append( config ); - boost::python::scope test = brain::exportSubmodule("test"); + boost::python::scope test = brain::exportSubmodule( "test" ); test.attr("blue_config") = bbp::test::getBlueconfig(); test.attr("blue_configs") = configs; test.attr("circuit_config") = bbp::test::getCircuitconfig(); + test.attr("root_data_path") = std::string( BBP_TESTDATA ); } } diff --git a/brain/spikeReportReader.cpp b/brain/spikeReportReader.cpp index d316e88..2e7a2ec 100644 --- a/brain/spikeReportReader.cpp +++ b/brain/spikeReportReader.cpp @@ -33,7 +33,12 @@ class SpikeReportReader::_Impl : _report( uri, brion::MODE_READ ) {} + _Impl( const brion::URI& uri, const GIDSet& subset ) + : _report( uri, subset ) + {} + brion::SpikeReport _report; + brion::Spikes _collected; }; SpikeReportReader::SpikeReportReader( const brion::URI& uri ) @@ -41,18 +46,55 @@ SpikeReportReader::SpikeReportReader( const brion::URI& uri ) { } +SpikeReportReader::SpikeReportReader( const brion::URI& uri, + const GIDSet& subset ) + : _impl( new _Impl( uri, subset )) +{ +} + SpikeReportReader::~SpikeReportReader() { delete _impl; } -brion::Spikes SpikeReportReader::getSpikes( const float startTime, - const float endTime ) +Spikes SpikeReportReader::getSpikes( const float startTime, + const float endTime ) +{ + if( endTime <= startTime ) + LBTHROW( std::logic_error( + "Start time should be strictly inferior to end time" )); + + if( _impl->_report.supportsBackwardSeek( )) + { + _impl->_report.seek( startTime ).get(); + return _impl->_report.readUntil( endTime ).get(); + } + + // In reports that don't support seek we just want to move forward at + // least until end time, we'll find the window later. + // We use read instead of readUntil so the end time gets updated with + // the latest value possible. We also try to read always, even if all spikes + // in the requested window have been already collected. + auto& collected = _impl->_collected; + auto spikes = _impl->_report.read( endTime ).get(); + if( !spikes.empty( )) + { + collected.reserve( collected.size() + spikes.size( )); + collected.insert( collected.end(), spikes.begin(), spikes.end( )); + } + + return Spikes( + std::lower_bound( collected.begin(), collected.end(), startTime, + []( const Spike& spike, const float val ){ + return spike.first < val; }), + std::lower_bound( collected.begin(), collected.end(), endTime, + []( const Spike& spike, const float val ){ + return spike.first < val; })); +} + +float SpikeReportReader::getEndTime() const { - if(endTime <= startTime) - LBTHROW(std::logic_error("Start time should be strictly inferior to end time")); - _impl->_report.seek( startTime ).get(); - return _impl->_report.readUntil( endTime ).get(); + return _impl->_report.getEndTime(); } bool SpikeReportReader::hasEnded() const diff --git a/brain/spikeReportReader.h b/brain/spikeReportReader.h index 1821233..75e2aa2 100644 --- a/brain/spikeReportReader.h +++ b/brain/spikeReportReader.h @@ -22,8 +22,10 @@ #ifndef BRAIN_SPIKEREPORTREADER_H #define BRAIN_SPIKEREPORTREADER_H +#include +#include + #include -#include namespace brain { @@ -45,13 +47,24 @@ class SpikeReportReader : public boost::noncopyable * @version 1.0 * @throw std::runtime_error if source is invalid. */ - explicit SpikeReportReader( const brion::URI& uri ); + BRAIN_API explicit SpikeReportReader( const brion::URI& uri ); + + /** + * Construct a new reader opening a spike data source. + * @param uri URI to spike report (can contain a wildcard to specify several + * files). + * @param subset Subset of cells to be reported. + * files). + * @version 1.0 + * @throw std::runtime_error if source is invalid. + */ + BRAIN_API SpikeReportReader( const brion::URI& uri, const GIDSet& subset ); /** * Destructor. * @version 1.0 */ - ~SpikeReportReader(); + BRAIN_API ~SpikeReportReader(); /** * Get all spikes inside a time window. @@ -64,7 +77,17 @@ class SpikeReportReader : public boost::noncopyable * @throw std::logic_error if the precondition is not fulfilled. * @version 1.0 */ - brion::Spikes getSpikes( const float start, const float end ); + BRAIN_API Spikes getSpikes( const float start, const float end ); + + /** + * @return the end timestamp of the report. This is the timestamp of the + * last spike known to be available or larger if the implementation + * has more metadata available. + * For stream reports this time is 0 and it is updated when + * getSpikes is called. + * @version 1.0 + */ + BRAIN_API float getEndTime() const; /** * @return true if any of the versions of getSpikes() reaches the end @@ -72,8 +95,7 @@ class SpikeReportReader : public boost::noncopyable called. * @version 1.0 */ - bool hasEnded() const; - + BRAIN_API bool hasEnded() const; /** * Close the data source. @@ -85,7 +107,7 @@ class SpikeReportReader : public boost::noncopyable * * @version 1.0 */ - void close(); + BRAIN_API void close(); private: class _Impl; diff --git a/brain/spikeReportWriter.h b/brain/spikeReportWriter.h index 691da3c..c6b23ac 100644 --- a/brain/spikeReportWriter.h +++ b/brain/spikeReportWriter.h @@ -60,7 +60,7 @@ class SpikeReportWriter : public boost::noncopyable * @param spikes Spikes to write. * @version 1.0 */ - void writeSpikes( const brion::Spikes& spikes ); + void writeSpikes( const Spikes& spikes ); /** * Get the URI where the writer is publishing. It could be same as the one diff --git a/brain/types.h b/brain/types.h index 1d71a5d..8bae721 100644 --- a/brain/types.h +++ b/brain/types.h @@ -41,7 +41,6 @@ enum class SynapsePrefetch }; class Circuit; -class Spikes; class SpikeReportReader; class SpikeReportWriter; class Synapse; @@ -67,6 +66,8 @@ using brion::uint32_ts; using brion::size_ts; using brion::SectionOffsets; +using brion::Spike; +using brion::Spikes; using brion::CompartmentCounts; typedef std::vector< Matrix4f > Matrix4fs; diff --git a/brion/plugin/spikeReportASCII.cpp b/brion/plugin/spikeReportASCII.cpp index fc2e2a1..5e0b573 100644 --- a/brion/plugin/spikeReportASCII.cpp +++ b/brion/plugin/spikeReportASCII.cpp @@ -29,6 +29,7 @@ #include #include +#include namespace brion { @@ -248,8 +249,10 @@ void SpikeReportASCII::append( const Spikes& spikes, const WriteFunc& writefunc file.flush(); - _currentTime = spikes.rbegin()->first + - std::numeric_limits< float >::epsilon(); + const float lastTimestamp = spikes.rbegin()->first; + _currentTime = std::nextafter( lastTimestamp, + std::numeric_limits< float >::max( )); + _endTime = std::max(_endTime, lastTimestamp); } } diff --git a/brion/plugin/spikeReportASCII.h b/brion/plugin/spikeReportASCII.h index 68305cf..657f4d1 100644 --- a/brion/plugin/spikeReportASCII.h +++ b/brion/plugin/spikeReportASCII.h @@ -40,6 +40,7 @@ class SpikeReportASCII : public SpikeReportPlugin Spikes readUntil(float toTimeStamp) final; void readSeek(float toTimeStamp) final; void writeSeek(float toTimeStamp) final; + bool supportsBackwardSeek() const final { return true; } protected: Spikes _spikes; diff --git a/brion/plugin/spikeReportBinary.cpp b/brion/plugin/spikeReportBinary.cpp index f72bf2a..1cd986a 100644 --- a/brion/plugin/spikeReportBinary.cpp +++ b/brion/plugin/spikeReportBinary.cpp @@ -113,6 +113,11 @@ SpikeReportBinary::SpikeReportBinary( const SpikeReportInitData& initData ) _memFile.reset( new BinaryReportMap( getURI().getPath( ))); else _memFile.reset( new BinaryReportMap( getURI().getPath(), 0 )); + + const Spike* spikeArray = _memFile->getReadableSpikes(); + const size_t nElems = _memFile->getNumSpikes(); + if( nElems != 0 ) + _endTime = spikeArray[nElems - 1].first; } bool SpikeReportBinary::handles( const SpikeReportInitData& initData ) @@ -155,7 +160,7 @@ Spikes SpikeReportBinary::readUntil( const float max ) for ( ; _startIndex < nElems; ++_startIndex ) { - if ( spikeArray[_startIndex].first > max ) + if ( spikeArray[_startIndex].first >= max ) { _currentTime = spikeArray[_startIndex].first; break; @@ -226,8 +231,10 @@ void SpikeReportBinary::write( const Spikes& spikes ) for( const Spike& spike : spikes ) spikeArray[_startIndex++] = spike; - _currentTime = spikes.rbegin()->first + - std::numeric_limits< float >::epsilon(); + const float lastTimestamp = spikes.rbegin()->first; + _currentTime = std::nextafter( lastTimestamp, + std::numeric_limits< float >::max( )); + _endTime = std::max(_endTime, lastTimestamp); } } } // namespaces diff --git a/brion/plugin/spikeReportBinary.h b/brion/plugin/spikeReportBinary.h index 1c981a7..533423e 100644 --- a/brion/plugin/spikeReportBinary.h +++ b/brion/plugin/spikeReportBinary.h @@ -52,6 +52,7 @@ class SpikeReportBinary : public SpikeReportPlugin void readSeek( float toTimeStamp ) final; void writeSeek( float toTimeStamp ) final; void write( const Spikes& spikes ) final; + bool supportsBackwardSeek() const final { return true; } private: std::unique_ptr< BinaryReportMap > _memFile; diff --git a/brion/plugin/spikeReportBluron.cpp b/brion/plugin/spikeReportBluron.cpp index bf1109f..995b4cc 100644 --- a/brion/plugin/spikeReportBluron.cpp +++ b/brion/plugin/spikeReportBluron.cpp @@ -56,6 +56,9 @@ SpikeReportBluron::SpikeReportBluron( const SpikeReportInitData& initData ) } _lastReadPosition = _spikes.begin(); + + if( !_spikes.empty( )) + _endTime = _spikes.rbegin()->first; } bool SpikeReportBluron::handles( const SpikeReportInitData& initData ) diff --git a/brion/plugin/spikeReportNEST.cpp b/brion/plugin/spikeReportNEST.cpp index b5fea84..0c55851 100644 --- a/brion/plugin/spikeReportNEST.cpp +++ b/brion/plugin/spikeReportNEST.cpp @@ -99,6 +99,8 @@ SpikeReportNEST::SpikeReportNEST( const SpikeReportInitData& initData ) } _lastReadPosition = _spikes.begin(); + if( !_spikes.empty( )) + _endTime = _spikes.rbegin()->first; } bool SpikeReportNEST::handles( const SpikeReportInitData& initData ) diff --git a/brion/plugin/spikeReportNEST.h b/brion/plugin/spikeReportNEST.h index 5a68b3b..dbb20e8 100644 --- a/brion/plugin/spikeReportNEST.h +++ b/brion/plugin/spikeReportNEST.h @@ -40,7 +40,7 @@ class SpikeReportNEST : public SpikeReportASCII static std::string getDescription(); void close() final; - virtual void write( const Spikes& spikes ) final; + void write( const Spikes& spikes ) final; }; } } diff --git a/brion/spikeReport.cpp b/brion/spikeReport.cpp index 11548c1..f48e9dc 100644 --- a/brion/spikeReport.cpp +++ b/brion/spikeReport.cpp @@ -148,6 +148,11 @@ float SpikeReport::getCurrentTime() const return _impl->plugin->getCurrentTime(); } +float SpikeReport::getEndTime() const +{ + return _impl->plugin->getEndTime(); +} + SpikeReport::State SpikeReport::getState() const { return _impl->plugin->getState(); @@ -175,12 +180,6 @@ std::future< Spikes > SpikeReport::read( float min ) _impl->plugin->_checkCanRead(); _impl->plugin->_checkStateOk(); - if ( min < getCurrentTime() ) - { - LBTHROW( std::logic_error( - "Can't read to a time stamp inferior to the current time" ) ); - } - if ( _impl->threadPool.hasPendingJobs() ) { LBTHROW( std::runtime_error( "Can't read: Pending read operation" ) ); @@ -256,4 +255,9 @@ void SpikeReport::write( const Spikes& spikes ) _impl->plugin->write( spikes ); } + +bool SpikeReport::supportsBackwardSeek() const +{ + return _impl->plugin->supportsBackwardSeek(); +} } diff --git a/brion/spikeReport.h b/brion/spikeReport.h index 95e6270..f6493c5 100644 --- a/brion/spikeReport.h +++ b/brion/spikeReport.h @@ -161,6 +161,16 @@ class SpikeReport BRION_API float getCurrentTime() const; /** + * @return the end timestamp of the report. This is the timestamp of the + * last spike known to be available or written or larger if the + * implementation has more metadata available. + * For stream reports this time is 0 before any operation is + * completed. + * @version 2.0 + */ + BRION_API float getEndTime() const; + + /** * @return The state after the last completed operation. * @version 2.0 */ @@ -179,7 +189,6 @@ class SpikeReport * - The report was open in read mode. * - There is no previous read or seek operation with a pending * future. - * - min >= getCurrentTime() or UNDEFINED_TIMESTAMP. * * Postconditions: * Let: @@ -285,6 +294,12 @@ class SpikeReport */ BRION_API void write( const Spikes& spikes ); + /** + * @return Whether the report supports seek to t < getCurrentTime() or not. + * @version 2.0 + */ + BRION_API bool supportsBackwardSeek() const; + private: std::unique_ptr< detail::SpikeReport > _impl; diff --git a/brion/spikeReportPlugin.h b/brion/spikeReportPlugin.h index 5e28a7c..bb56cdb 100644 --- a/brion/spikeReportPlugin.h +++ b/brion/spikeReportPlugin.h @@ -139,6 +139,9 @@ class SpikeReportPlugin : public boost::noncopyable "Operation not supported in spike report plugin" )); } + /** @copydoc brion::SpikeReport::supportsBackwardSeek */ + virtual bool supportsBackwardSeek() const = 0; + void setFilter( const GIDSet& ids ) { _idsSubset = ids; @@ -168,6 +171,11 @@ class SpikeReportPlugin : public boost::noncopyable return _currentTime; } + virtual float getEndTime() const + { + return _endTime; + } + bool isClosed() const { return _closed; @@ -187,6 +195,7 @@ class SpikeReportPlugin : public boost::noncopyable brion::GIDSet _idsSubset; int _accessMode = brion::MODE_READ; float _currentTime = 0; + float _endTime = 0; State _state = State::ok; void pushBack( const Spike& spike, Spikes& spikes ) const diff --git a/doc/Changelog.md b/doc/Changelog.md index c76962f..d967988 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -3,6 +3,10 @@ Changelog {#Changelog} # git master +* [120](https://github.com/BlueBrain/Brion/pull/120), [131](https://github.com/BlueBrain/Brion/pull/131): + - New SpikeReport API. + - Reimplementation of the high level SpikeReportReader. The new implementation + uses numpy arrays to provide the requested spikes. * [126](https://github.com/BlueBrain/Brion/pull/126): Add erase for map compartment reports * [122](https://github.com/BlueBrain/Brion/pull/122): diff --git a/tests/brain/python/CMakeLists.txt b/tests/brain/python/CMakeLists.txt index fd90bf4..4372de7 100644 --- a/tests/brain/python/CMakeLists.txt +++ b/tests/brain/python/CMakeLists.txt @@ -3,7 +3,7 @@ # # This file is part of Brion # -# Change this number when adding tests to force a CMake run: 1 +# Change this number when adding tests to force a CMake run: 2 if(NOT TARGET BBPTestData OR NOT TARGET brain_python) return() diff --git a/tests/brain/python/spikes.py b/tests/brain/python/spikes.py new file mode 100644 index 0000000..59a7c06 --- /dev/null +++ b/tests/brain/python/spikes.py @@ -0,0 +1,79 @@ +# Copyright (c) 2017, EPFL/Blue Brain Project +# Juan Hernando +# +# This file is part of Brion +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License version 3.0 as published +# by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import setup + +import os +import numpy +import brain + +import unittest + +class TestSpikeReportReader(unittest.TestCase): + + def setUp(self): + self.filename = brain.test.root_data_path + \ + "/local/simulations/may17_2011/Control/out.spikes" + + def test_creation_bad(self): + self.assertRaises(RuntimeError, lambda: brain.SpikeReportReader("foo")) + + def test_creation(self): + reader = brain.SpikeReportReader(self.filename) + + def test_get_spikes_bad(self): + reader = brain.SpikeReportReader(self.filename) + self.assertRaises(RuntimeError, lambda: reader.get_spikes(0, 0)) + self.assertRaises(RuntimeError, lambda: reader.get_spikes(0, -1)) + + def test_get_spikes(self): + reader = brain.SpikeReportReader(self.filename) + spikes = reader.get_spikes(-10, 0) + assert(len(spikes) == 0) + + spikes = reader.get_spikes(1, 5) + assert(spikes[0][0] >= 1) + assert(spikes[-1][0] < 5) + + spikes = reader.get_spikes(7, 9) + assert(spikes[0][0] >= 7) + assert(spikes[-1][0] < 9) + + def test_get_spikes_filtered(self): + gids = {1, 10, 100} + reader = brain.SpikeReportReader(self.filename, gids) + spikes = reader.get_spikes(0, float("inf")) + for time, gid in spikes: + assert(gid in gids) + + def test_properties(self): + reader = brain.SpikeReportReader(self.filename) + # assertAlmostEqual fails due to a float <-> double conversion error + assert(round(reader.end_time - 9.975, 6) == 0) + + # Until some read operation beyond the end is performed the report is + # not ended + assert(not reader.has_ended) + # numpy.nextafter(reader.end_time) should be sufficient, but the result + # can't be distinguished from end_time after demoted to float in the C++ + # side. + spikes = reader.get_spikes(0, float("inf")) + assert(reader.has_ended) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/brain/spikeReportReaderWriter.cpp b/tests/brain/spikeReportReaderWriter.cpp index ec8f9b1..07578ee 100644 --- a/tests/brain/spikeReportReaderWriter.cpp +++ b/tests/brain/spikeReportReaderWriter.cpp @@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE( test_simple_load_static ) -BOOST_AUTO_TEST_CASE( test_simple_read ) +BOOST_AUTO_TEST_CASE( test_simple_read_bluron ) { boost::filesystem::path path( BBP_TESTDATA ); path /= BLURON_SPIKE_REPORT_FILE; @@ -119,6 +119,35 @@ BOOST_AUTO_TEST_CASE( test_simple_read ) BOOST_CHECK_EQUAL( ( --spikes.end( ))->second, BLURON_LAST_SPIKE_GID ); } +BOOST_AUTO_TEST_CASE( test_simple_read_nest ) +{ + boost::filesystem::path path( BBP_TESTDATA ); + path /= NEST_SPIKE_REPORT_FILE; + + brain::SpikeReportReader reader( brion::URI( path.string( ))); + const brion::Spikes& spikes = reader.getSpikes(0,brion::UNDEFINED_TIMESTAMP); + + BOOST_REQUIRE_EQUAL( spikes.size(), NEST_SPIKES_COUNT ); + + BOOST_CHECK_EQUAL( spikes.begin()->first, NEST_FIRST_SPIKE_TIME ); + BOOST_CHECK_EQUAL( spikes.begin()->second, NEST_FIRST_SPIKE_GID ); + + BOOST_CHECK_EQUAL( ( --spikes.end( ))->first, NEST_LAST_SPIKE_TIME ); + BOOST_CHECK_EQUAL( ( --spikes.end( ))->second, NEST_LAST_SPIKE_GID ); +} + +BOOST_AUTO_TEST_CASE( test_simple_read_filtered ) +{ + boost::filesystem::path path( BBP_TESTDATA ); + path /= BLURON_SPIKE_REPORT_FILE; + + brain::GIDSet gids{ 1, 10, 100 }; + brain::SpikeReportReader reader( brion::URI( path.string( )), gids ); + const auto spikes = reader.getSpikes(0,brion::UNDEFINED_TIMESTAMP); + BOOST_REQUIRE( !spikes.empty( )); + for (auto spike : spikes ) + BOOST_CHECK( gids.find( spike.second ) != gids.end()); +} BOOST_AUTO_TEST_CASE( test_closed_window ) { @@ -140,24 +169,6 @@ BOOST_AUTO_TEST_CASE( test_out_of_window ) BOOST_CHECK_THROW( reader.getSpikes(start, start + 1 ), std::logic_error ); } -BOOST_AUTO_TEST_CASE( test_simple_stream_read ) -{ - boost::filesystem::path path( BBP_TESTDATA ); - path /= NEST_SPIKE_REPORT_FILE; - - brain::SpikeReportReader reader( brion::URI( path.string( ))); - - const brion::Spikes& spikes = reader.getSpikes(0,brion::UNDEFINED_TIMESTAMP); - - BOOST_REQUIRE_EQUAL( spikes.size(), NEST_SPIKES_COUNT ); - - BOOST_CHECK_EQUAL( spikes.begin()->first, NEST_FIRST_SPIKE_TIME ); - BOOST_CHECK_EQUAL( spikes.begin()->second, NEST_FIRST_SPIKE_GID ); - - BOOST_CHECK_EQUAL( ( --spikes.end( ))->first, NEST_LAST_SPIKE_TIME ); - BOOST_CHECK_EQUAL( ( --spikes.end( ))->second, NEST_LAST_SPIKE_GID ); -} - BOOST_AUTO_TEST_CASE( test_moving_window ) { boost::filesystem::path path( BBP_TESTDATA ); @@ -173,7 +184,7 @@ BOOST_AUTO_TEST_CASE( test_moving_window ) { BOOST_CHECK( spikes.begin()->first >= start ); - BOOST_CHECK( ( --spikes.end( ))->first >= start ); + BOOST_CHECK( spikes.rbegin()->first < start + 1 ); } start += 1; } diff --git a/tests/spikeReport.cpp b/tests/spikeReport.cpp index bff7e05..f5aca8e 100644 --- a/tests/spikeReport.cpp +++ b/tests/spikeReport.cpp @@ -30,7 +30,6 @@ #define BLURON_SPIKE_REPORT_FILE "local/simulations/may17_2011/Control/out.dat" #define BINARY_SPIKE_REPORT_FILE "local/simulations/may17_2011/Control/out.spikes" - #define BLURON_SPIKES_START_TIME 0.15f #define BLURON_SPIKES_END_TIME 9.975f @@ -186,15 +185,33 @@ BOOST_AUTO_TEST_CASE( invoke_invalid_method_bluron ) BOOST_AUTO_TEST_CASE( invoke_invalid_method_nest ) { boost::filesystem::path path( BBP_TESTDATA ); - const std::string& files = "NESTSpikeData/spike_detector-65537-*.gdf"; - - brion::SpikeReport report( - brion::URI(( path / files ).string( )), - brion::MODE_READ ); + const std::string files( "NESTSpikeData/spike_detector-65537-*.gdf" ); + brion::SpikeReport report( brion::URI(( path / files ).string( )), + brion::MODE_READ ); BOOST_CHECK_THROW( report.write( brion::Spikes {} ), std::runtime_error ); } +BOOST_AUTO_TEST_CASE( end_time ) +{ + boost::filesystem::path path( BBP_TESTDATA ); + + const std::string filename1( "NESTSpikeData/spike_detector-65537-*.gdf" ); + brion::SpikeReport report1( brion::URI(( path / filename1 ).string( )), + brion::MODE_READ ); + BOOST_CHECK_EQUAL( report1.getEndTime(), 98.90f ); + + const std::string filename2( BLURON_SPIKE_REPORT_FILE ); + brion::SpikeReport report2( brion::URI(( path / filename2 ).string( )), + brion::MODE_READ ); + BOOST_CHECK_EQUAL( report2.getEndTime(), 9.975f ); + + const std::string filename3( BINARY_SPIKE_REPORT_FILE ); + brion::SpikeReport report3( brion::URI(( path / filename3 ).string( )), + brion::MODE_READ ); + BOOST_CHECK_EQUAL( report3.getEndTime(), 9.975f ); +} + inline void testWrite (const char * format) { TemporaryData data {format}; @@ -373,14 +390,18 @@ inline void testReadUntil(const char * format) brion::SpikeReport reportRead( brion::URI( data.tmpFileName ), brion::MODE_READ ); - auto spikes = reportRead.readUntil( 0.25 ).get(); - BOOST_CHECK_EQUAL( spikes.size(), 3); - BOOST_CHECK( reportRead.getCurrentTime() >= 0.25f ); - BOOST_CHECK( spikes.rbegin()->first < 0.25 ); + auto spikes = reportRead.readUntil( 0.15 ).get(); + BOOST_CHECK_EQUAL( spikes.size(), 1 ); + BOOST_CHECK( reportRead.getCurrentTime() >= 0.15f ); + + spikes = reportRead.readUntil( 0.3 ).get(); + BOOST_CHECK_EQUAL( spikes.size(), 2); + BOOST_CHECK( reportRead.getCurrentTime() >= 0.3f ); + BOOST_CHECK( spikes.rbegin()->first < 0.3 ); BOOST_CHECK_EQUAL( reportRead.getState(), brion::SpikeReport::State::ok ); spikes = reportRead.read( brion::UNDEFINED_TIMESTAMP ).get(); - BOOST_CHECK_EQUAL( spikes.size(), 2); + BOOST_CHECK_EQUAL( spikes.size(), 2 ); BOOST_CHECK_EQUAL( reportRead.getCurrentTime(), brion::UNDEFINED_TIMESTAMP ); BOOST_CHECK_EQUAL( reportRead.getState(), brion::SpikeReport::State::ended ); } @@ -397,10 +418,10 @@ inline void testReadUntilFiltered(const char * format) brion::SpikeReport reportRead( brion::URI( data.tmpFileName ), brion::GIDSet{ 22, 25 }); - auto spikes = reportRead.readUntil( 0.25 ).get(); + auto spikes = reportRead.readUntil( 0.4 ).get(); BOOST_CHECK_EQUAL( spikes.size(), 1 ); - BOOST_CHECK( reportRead.getCurrentTime() >= 0.25f ); - BOOST_CHECK( spikes.rbegin()->first < 0.25 ); + BOOST_CHECK( reportRead.getCurrentTime() >= 0.4f ); + BOOST_CHECK( spikes.rbegin()->first < 0.4 ); BOOST_CHECK_EQUAL( reportRead.getState(), brion::SpikeReport::State::ok ); spikes = reportRead.readUntil( brion::UNDEFINED_TIMESTAMP ).get(); @@ -455,6 +476,7 @@ inline void testReadSeek(const char* format) brion::SpikeReport reportRead{ brion::URI( data.tmpFileName ), brion::MODE_READ }; + BOOST_CHECK( reportRead.supportsBackwardSeek( )); reportRead.seek( 0.3f ).get(); BOOST_CHECK_EQUAL( reportRead.getCurrentTime(), 0.3f ); @@ -516,7 +538,7 @@ inline void testInvalidRead(const char * format) brion::MODE_READ }; reportRead.readUntil( 0.3 ).get(); - BOOST_CHECK_THROW( reportRead.read( 0.1 ), std::logic_error ); + BOOST_CHECK_NO_THROW( reportRead.read( 0.1 )); BOOST_CHECK_THROW( reportRead.readUntil( 0.1 ), std::logic_error ); }