diff --git a/CMakeLists.txt b/CMakeLists.txt index 840e1db..e845ec0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMake include(GitExternal) set(VERSION_MAJOR "1") -set(VERSION_MINOR "6") +set(VERSION_MINOR "7") set(VERSION_PATCH "0") set(VERSION_ABI 5) diff --git a/brion/blueConfig.cpp b/brion/blueConfig.cpp index 9cdf376..fcc55d5 100644 --- a/brion/blueConfig.cpp +++ b/brion/blueConfig.cpp @@ -19,12 +19,16 @@ #include "blueConfig.h" +#include +#include +#include +#include +#include #include #include #include -#include -#include +namespace fs = boost::filesystem; namespace boost { template<> @@ -73,6 +77,7 @@ typedef stde::hash_map< std::string, KVStore > ValueTable; namespace detail { + class BlueConfig { public: @@ -115,7 +120,7 @@ class BlueConfig continue; } - _names[type].push_back( name ); + names[type].push_back( name ); Strings lines; boost::split( lines, content, boost::is_any_of( "\n" ), @@ -137,18 +142,71 @@ class BlueConfig std::string value = line.substr( pos+1 ); boost::trim( value ); - _table[type][name].insert( std::make_pair( line.substr( 0, pos), + table[type][name].insert( std::make_pair( line.substr( 0, pos), value )); } } - if( _table[CONFIGSECTION_RUN].empty( )) + if( table[CONFIGSECTION_RUN].empty( )) LBTHROW( std::runtime_error( source + " not a valid BlueConfig file" )); } - Strings _names[CONFIGSECTION_ALL]; - ValueTable _table[CONFIGSECTION_ALL]; + std::string getRun() + { + const brion::Strings& runs = names[ brion::CONFIGSECTION_RUN ]; + return runs.empty() ? std::string() : runs.front(); + } + + const std::string& get( const BlueConfigSection section, + const std::string& sectionName, + const std::string& key ) const + { + // This function doesn't create entries in the tables in case they + // don't exist. + static std::string empty; + const ValueTable::const_iterator tableIt = + table[section].find( sectionName ); + if( tableIt == table[section].end( )) + return empty; + const KVStore& store = tableIt->second; + const KVStore::const_iterator kv = store.find( key ); + if( kv == store.end( )) + return empty; + return kv->second; + } + + const std::string& getCircuitTarget() + { + return get( brion::CONFIGSECTION_RUN, getRun(), + BLUECONFIG_CIRCUIT_TARGET_KEY ); + } + + const std::string& getOutputRoot() + { + return get( brion::CONFIGSECTION_RUN, getRun(), + BLUECONFIG_OUTPUT_PATH_KEY ); + } + + + template + bool get( const BlueConfigSection section, const std::string& sectionName, + const std::string& key, T& value ) const + { + try + { + value = boost::lexical_cast< T >( get( section, sectionName, key )); + } + catch( const boost::bad_lexical_cast& ) + { + return false; + } + return true; + } + + + Strings names[CONFIGSECTION_ALL]; + ValueTable table[CONFIGSECTION_ALL]; }; } @@ -162,17 +220,120 @@ BlueConfig::~BlueConfig() delete _impl; } -const Strings& -BlueConfig::getSectionNames( const BlueConfigSection section ) const +const Strings& BlueConfig::getSectionNames( const BlueConfigSection section ) + const { - return _impl->_names[section]; + return _impl->names[section]; } const std::string& BlueConfig::get( const BlueConfigSection section, const std::string& sectionName, const std::string& key ) const { - return _impl->_table[section][sectionName][key]; + return _impl->get( section, sectionName, key ); +} + +URI BlueConfig::getCircuitSource() const +{ + URI uri; + uri.setScheme("file"); + uri.setPath( get( CONFIGSECTION_RUN, _impl->getRun(), + BLUECONFIG_CIRCUIT_PATH_KEY ) + CIRCUIT_FILE ); + return uri; +} + + +URI BlueConfig::getSynapseSource() const +{ + URI uri; + uri.setScheme("file"); + uri.setPath( get( CONFIGSECTION_RUN, _impl->getRun(), + BLUECONFIG_NRN_PATH_KEY )); + return uri; +} + +URI BlueConfig::getMorphologySource() const +{ + URI uri; + uri.setScheme("file"); + std::string bare = get( CONFIGSECTION_RUN, _impl->getRun(), + BLUECONFIG_MORPHOLOGY_PATH_KEY ); + const fs::path barePath( bare ); + const fs::path guessedPath = barePath / MORPHOLOGY_HDF5_FILES_SUBDIRECTORY; + if( fs::exists( guessedPath ) && fs::is_directory( guessedPath )) + uri.setPath( guessedPath.string( )); + else + uri.setPath( bare ); + + return uri; +} + +URI BlueConfig::getReportSource( const std::string& report ) const +{ + std::string format = get( CONFIGSECTION_REPORT, report, + BLUECONFIG_REPORT_FORMAT_KEY ); + if( format.empty( )) + { + LBWARN << "Invalid or missing report " << report << std::endl; + return URI(); + } + + boost::algorithm::to_lower( format ); + + if( format == "binary" || format == "bin" ) + return URI( std::string( "file://" ) + _impl->getOutputRoot() + "/" + + report + ".bbp" ); + + if( format == "hdf5" || format.empty() || fs::is_directory( format )) + return URI( std::string( "file://" ) + _impl->getOutputRoot() + "/" + + report + ".h5" ); + + if( format == "stream" || format == "leveldb" || format == "skv" ) + return URI( _impl->getOutputRoot( )); + + LBWARN << "Unknown report format " << format << std::endl; + return URI(); +} + +URI BlueConfig::getSpikeSource() const +{ + std::string path = get( CONFIGSECTION_RUN, _impl->getRun(), + BLUECONFIG_SPIKES_PATH_KEY ); + if( path.empty( )) + path = _impl->getOutputRoot() + SPIKE_FILE; + URI uri; + uri.setScheme( "file" ); + uri.setPath( path ); + return uri; +} + +std::string BlueConfig::getCircuitTarget() const +{ + return _impl->getCircuitTarget(); +} + +GIDSet BlueConfig::parseTarget( std::string target ) const +{ + if( target.empty( )) + target = _impl->getCircuitTarget(); + + const std::string& run = _impl->getRun(); + brion::Targets targets; + targets.push_back( brion::Target( + get( brion::CONFIGSECTION_RUN, run, BLUECONFIG_NRN_PATH_KEY ) + + CIRCUIT_TARGET_FILE )); + targets.push_back( brion::Target( + get( brion::CONFIGSECTION_RUN, run, BLUECONFIG_TARGET_FILE_KEY ))); + return brion::Target::parse( targets, target ); +} + +float BlueConfig::getTimestep() const +{ + const std::string& run = _impl->getRun(); + float timestep = std::numeric_limits::quiet_NaN(); + _impl->get< float >( brion::CONFIGSECTION_RUN, run, BLUECONFIG_DT_KEY, + timestep ); + return timestep; } std::ostream& operator << ( std::ostream& os, const BlueConfig& config ) @@ -180,7 +341,7 @@ std::ostream& operator << ( std::ostream& os, const BlueConfig& config ) for( size_t i = 0; i < CONFIGSECTION_ALL; ++i ) { BOOST_FOREACH( const ValueTable::value_type& entry, - config._impl->_table[i] ) + config._impl->table[i] ) { os << boost::lexical_cast< std::string >( BlueConfigSection( i )) << " " << entry.first << std::endl; diff --git a/brion/blueConfig.h b/brion/blueConfig.h index fce737e..bbb1695 100644 --- a/brion/blueConfig.h +++ b/brion/blueConfig.h @@ -92,6 +92,51 @@ class BlueConfig : public boost::noncopyable } //@} + /** @name Semantic read API */ + //@{ + /** @return the URI to the circuit information. @sa Circuit + * @version 1.7 + */ + BRION_API URI getCircuitSource() const; + + /** @return the URI to the location of synapse nrn files. + * @version 1.7 + */ + BRION_API URI getSynapseSource() const; + + /** @return the full path to the morphology database. A suffix may be + * prepended to the to the bare path from the BlueConfig. + * @version 1.7 + */ + BRION_API URI getMorphologySource() const; + + /** @return the URI to the named report. @sa CompartmentReport + * @version 1.7 + */ + BRION_API URI getReportSource( const std::string& report ) const; + + /** @return the URI to the default spike data source. @sa SpikeReport + * @version 1.7 + */ + BRION_API URI getSpikeSource() const; + + /** @return the name of the circuit target + * @version 1.7 + */ + std::string getCircuitTarget() const; + + /** @return the set of GIDs for the given target, or the circuit target if + * the given target is empty. + * @version 1.7 + */ + BRION_API brion::GIDSet parseTarget( std::string target ) const; + + /** @return the simulation timestep in ms or NaN if undefined. + * @version 1.7 + */ + BRION_API float getTimestep() const; + //@} + private: friend std::ostream& operator << ( std::ostream&, const BlueConfig& ); diff --git a/brion/circuit.cpp b/brion/circuit.cpp index e3cbe34..3889590 100644 --- a/brion/circuit.cpp +++ b/brion/circuit.cpp @@ -28,8 +28,6 @@ namespace brion { -namespace detail -{ enum Section { @@ -44,10 +42,10 @@ enum Section SECTION_UNKNOWN }; -class Circuit +class Circuit::Impl { public: - explicit Circuit( const std::string& source ) + explicit Impl( const std::string& source ) { namespace fs = boost::filesystem; fs::path path = source; @@ -88,10 +86,6 @@ class Circuit } } - ~Circuit() - { - } - NeuronMatrix get( const GIDSet& gids, const uint32_t attributes ) const { const std::bitset< NEURON_ALL > bits( attributes ); @@ -207,10 +201,14 @@ class Circuit typedef stde::hash_map< uint32_t, Strings > CircuitTable; CircuitTable _table; }; -} Circuit::Circuit( const std::string& source ) - : _impl( new detail::Circuit( source )) + : _impl( new Impl( source )) +{ +} + +Circuit::Circuit( const URI& source ) + : _impl( new Impl( source.getPath( ))) { } diff --git a/brion/circuit.h b/brion/circuit.h index 6ba04e1..b165302 100644 --- a/brion/circuit.h +++ b/brion/circuit.h @@ -26,12 +26,10 @@ namespace brion { -namespace detail { class Circuit; } - /** Read access to a Circuit file. * - * Following RAII, this class is ready to use after the creation and will ensure - * release of resources upon destruction. + * This class loads the circuit data at creation and will ensure release of + * resources upon destruction. */ class Circuit : public boost::noncopyable { @@ -49,6 +47,14 @@ class Circuit : public boost::noncopyable */ BRION_API explicit Circuit( const std::string& source ); + /** Open given filepath to a circuit file for reading. + * + * @param source filepath to circuit file + * @throw std::runtime_error if file is not a valid circuit file + * @version 1.7 + */ + BRION_API explicit Circuit( const URI& source ); + /** Retrieve neuron attributes for set of neurons. * * @param gids set of neurons of interest; if empty, all neurons in the @@ -76,7 +82,8 @@ class Circuit : public boost::noncopyable //@} private: - detail::Circuit* const _impl; + class Impl; + Impl* const _impl; }; } diff --git a/brion/constants.h b/brion/constants.h new file mode 100644 index 0000000..2f42d35 --- /dev/null +++ b/brion/constants.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2013-2015, 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. + */ + +#ifndef BRION_CONSTANTS +#define BRION_CONSTANTS + +namespace brion +{ + +const char* const SPIKE_FILE = "/out.dat"; +const char* const CIRCUIT_FILE = "/circuit.mvd2"; +const char* const CIRCUIT_TARGET_FILE = "/start.target"; + +const char* const MORPHOLOGY_HDF5_FILES_SUBDIRECTORY = "h5"; + +const char* const BLUECONFIG_CIRCUIT_PATH_KEY = "CircuitPath"; +const char* const BLUECONFIG_NRN_PATH_KEY = "nrnPath"; +const char* const BLUECONFIG_MORPHOLOGY_PATH_KEY = "MorphologyPath"; +const char* const BLUECONFIG_OUTPUT_PATH_KEY = "OutputRoot"; +const char* const BLUECONFIG_TARGET_FILE_KEY = "TargetFile"; +const char* const BLUECONFIG_SPIKES_PATH_KEY = "SpikesPath"; + +const char* const BLUECONFIG_CIRCUIT_TARGET_KEY = "CircuitTarget"; +const char* const BLUECONFIG_REPORT_FORMAT_KEY = "Format"; +const char* const BLUECONFIG_DT_KEY = "Dt"; + +} + +#endif diff --git a/brion/spikeReportPlugin.h b/brion/spikeReportPlugin.h index b60ee5f..e21e777 100644 --- a/brion/spikeReportPlugin.h +++ b/brion/spikeReportPlugin.h @@ -70,7 +70,7 @@ typedef PluginInitData SpikeReportInitData; * * Plugin libraries in the LD_LIBRARY_PATH will be automatically registered if * they provide the abovementioned C functions and follow the naming convention: - * BrionSpikeReport. + * \Brion\SpikeReport.\ * * @version 1.4 */ diff --git a/brion/types.h b/brion/types.h index f1b5b25..db4e1da 100644 --- a/brion/types.h +++ b/brion/types.h @@ -58,11 +58,11 @@ class Synapse; class SynapseSummary; class Target; -typedef vmml::vector< 2, int32_t > Vector2i; //!< A two-component int32 vector -typedef vmml::vector< 3, float > Vector3f; //!< A three-component float vector -typedef vmml::vector< 4, float > Vector4f; //!< A four-component float vector -typedef vmml::vector< 3, double > Vector3d; //!< A three-component double vector -typedef vmml::vector< 4, double > Vector4d; //!< A four-component double vector +using vmml::Vector2i; +using vmml::Vector3f; +using vmml::Vector4f; +using vmml::Vector3d; +using vmml::Vector4d; typedef std::vector< size_t > size_ts; typedef std::vector< int32_t > int32_ts; @@ -124,7 +124,8 @@ const float RESTING_VOLTAGE = -67.; //!< Resting voltage in mV const float MINIMUM_VOLTAGE = -80.; //!< Lowest voltage after hyperpolarisation using lunchbox::Strings; -using lunchbox::URI; + +using servus::URI; } // if you have a type T in namespace N, the operator << for T needs to be in diff --git a/doc/Changelog.md b/doc/Changelog.md index a66cbd3..d0106bf 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -3,6 +3,8 @@ Changelog {#Changelog} # git master {#master) +* [29](https://github.com/BlueBrain/Brion/pull/29): + New member functions in brion::BlueConfig to provide a semantic API. * [28](https://github.com/BlueBrain/Brion/pull/28): SpikeReport continues parsing files that have broken lines diff --git a/tests/blueConfig.cpp b/tests/blueConfig.cpp index f1f5f82..6b18e14 100644 --- a/tests/blueConfig.cpp +++ b/tests/blueConfig.cpp @@ -152,3 +152,53 @@ BOOST_AUTO_TEST_CASE( test_verify_loaded_data ) BOOST_CHECK_EQUAL( config.get( brion::CONFIGSECTION_REPORT, "voltage", "EndTime" ), 99.f ); } + +BOOST_AUTO_TEST_CASE( semantic_api ) +{ + const std::string prefix( BBP_TESTDATA ); + const brion::BlueConfig config( bbp::test::getBlueconfig( )); + const std::string root = config.get( brion::CONFIGSECTION_RUN, "Demo", + "OutputRoot" ); + + BOOST_CHECK_EQUAL( config.getCircuitSource(), + brion::URI( prefix + "/local/circuits/18.10.10_600cell/circuit.mvd2" )); + BOOST_CHECK_EQUAL( config.getSynapseSource(), + brion::URI( prefix + "/local/circuits/18.10.10_600cell/ncsFunctionalCompare" )); + BOOST_CHECK_EQUAL( config.getMorphologySource(), + brion::URI( prefix + "/local/morphologies/01.07.08/h5" )); + + BOOST_CHECK_EQUAL( config.getReportSource( "unknown"), brion::URI( )); + const brion::URI allCompartments = + config.getReportSource( "allCompartments" ); + BOOST_CHECK_EQUAL( allCompartments.getScheme(), "file" ); + BOOST_CHECK_EQUAL( allCompartments.getPath(), + root + "/allCompartments.bbp" ); + + const brion::URI spikes = config.getSpikeSource(); + BOOST_CHECK_EQUAL( spikes.getScheme(), "file" ); + BOOST_CHECK_EQUAL( spikes.getPath(), root + "/out.dat" ); + + BOOST_CHECK_EQUAL( config.getCircuitTarget(), "Column" ); + BOOST_CHECK_EQUAL( config.getTimestep( ), 0.025f ); +} + +BOOST_AUTO_TEST_CASE( parse_target ) +{ + const brion::BlueConfig config( bbp::test::getBlueconfig( )); + const brion::GIDSet defaultTarget = config.parseTarget( "" ); + BOOST_CHECK( defaultTarget.size( )); + const brion::GIDSet columnTarget = config.parseTarget( "Column" ); + BOOST_CHECK( columnTarget.size( )); + // Can't use BOOST_CHECK_EQUAL because GIDSet lacks operator<< + BOOST_CHECK( defaultTarget == columnTarget ); + + const brion::GIDSet fromUserTarget = config.parseTarget( "AllL5CSPC" ); + BOOST_CHECK( fromUserTarget.size( )); + const brion::GIDSet fromStartTarget = config.parseTarget( "L5CSPC" ); + BOOST_CHECK( fromStartTarget.size( )); + BOOST_CHECK( fromStartTarget == fromUserTarget ); + + // Shouldn't this throw instead? + const brion::GIDSet empty = config.parseTarget( "unexistent" ); + BOOST_CHECK( empty.empty( )); +}