diff --git a/opm/parser/eclipse/CMakeLists.txt b/opm/parser/eclipse/CMakeLists.txt index 0da5ad0ea..d7e3b8cee 100644 --- a/opm/parser/eclipse/CMakeLists.txt +++ b/opm/parser/eclipse/CMakeLists.txt @@ -15,6 +15,7 @@ add_subdirectory(EclipseState/Util/tests) add_subdirectory(EclipseState/IOConfig/tests) add_subdirectory(EclipseState/InitConfig/tests) add_subdirectory(EclipseState/Summary/tests) +add_subdirectory(Utility/tests) add_subdirectory(Applications) add_subdirectory(IntegrationTests) @@ -134,6 +135,10 @@ EclipseState/Summary/Summary.cpp EclipseState/IOConfig/IOConfig.cpp) # +set( utility_source +Utility/Functional.cpp +) + set( HEADER_FILES OpmLog/LogBackend.hpp OpmLog/TimerLog.hpp @@ -280,6 +285,7 @@ EclipseState/Tables/TableEnums.hpp EclipseState/Tables/TableSchema.hpp EclipseState/Tables/TableIndex.hpp # +Utility/Functional.hpp Utility/EquilWrapper.hpp) add_library(buildParser ${rawdeck_source} ${build_parser_source} ${deck_source} ${unit_source} ${generator_source}) @@ -333,7 +339,7 @@ opm_add_test( runInlineKeywordTest SOURCES ${PROJECT_BINARY_DIR}/generated-sourc #----------------------------------------------------------------- -add_library(opmparser ${generated_source} ${state_source} ${rawdeck_source} ${parser_source} ${deck_source} ${unit_source} ${log_source} ${generator_source}) +add_library(opmparser ${generated_source} ${state_source} ${rawdeck_source} ${parser_source} ${deck_source} ${unit_source} ${log_source} ${generator_source} ${utility_source}) add_dependencies( opmparser generatedCode ) target_link_libraries(opmparser opmjson ${Boost_LIBRARIES} ${ERT_LIBRARIES}) set_target_properties(opmparser PROPERTIES VERSION ${opm-parser_VERSION_MAJOR}.${opm-parser_VERSION_MINOR} diff --git a/opm/parser/eclipse/EclipseState/Summary/Summary.cpp b/opm/parser/eclipse/EclipseState/Summary/Summary.cpp index 470a918ff..9877312be 100644 --- a/opm/parser/eclipse/EclipseState/Summary/Summary.cpp +++ b/opm/parser/eclipse/EclipseState/Summary/Summary.cpp @@ -9,51 +9,12 @@ #include #include #include +#include #include namespace Opm { - namespace fun { - - /* - * map :: (a -> b) -> [a] -> [b] - * - * C can be any foreach-compatible container (that supports .begin, - * .end), but will always return a vector. - */ - template< typename F, typename C > - std::vector< typename std::result_of< F( typename C::const_iterator::value_type& ) >::type > - map( F&& f, const C& src ) { - using A = typename C::const_iterator::value_type; - using B = typename std::result_of< F( A& ) >::type; - std::vector< B > ret; - ret.reserve( src.size() ); - - std::transform( src.begin(), src.end(), std::back_inserter( ret ), f ); - return ret; - } - - template< typename A > - std::vector< A > concat( std::vector< std::vector< A > >&& src ) { - - const auto size = std::accumulate( src.begin(), src.end(), 0, - []( std::size_t acc, const std::vector< A >& x ) { - return acc + x.size(); - } - ); - - std::vector< A > dst; - dst.reserve( size ); - - for( auto& x : src ) - std::move( x.begin(), x.end(), std::back_inserter( dst ) ); - - return dst; - } - - } - static std::string wellName( const std::shared_ptr< const Well >& well ) { return well->name(); } diff --git a/opm/parser/eclipse/Utility/Functional.cpp b/opm/parser/eclipse/Utility/Functional.cpp new file mode 100644 index 000000000..3e05577ca --- /dev/null +++ b/opm/parser/eclipse/Utility/Functional.cpp @@ -0,0 +1,49 @@ +#include + +namespace Opm { +namespace fun { + + iota::iota( int begin, int end ) : first( begin ), last( end ) {} + + iota::iota( int end ) : iota( 0, end ) {} + + size_t iota::size() const { + return this->last - this->first; + } + + iota::const_iterator iota::begin() const { + return { first }; + } + + iota::const_iterator iota::end() const { + return { last }; + } + + int iota::const_iterator::operator*() const { + return this->value; + } + + iota::const_iterator& iota::const_iterator::operator++() { + ++( this->value ); + return *this; + } + + iota::const_iterator iota::const_iterator::operator++( int ) { + iota::const_iterator copy( *this ); + this->operator++(); + return copy; + } + + bool iota::const_iterator::operator==( const const_iterator& rhs ) const { + return this->value == rhs.value; + } + + bool iota::const_iterator::operator!=( const const_iterator& rhs ) const { + return !(*this == rhs ); + } + + iota::const_iterator::const_iterator( int x ) : value( x ) {} + + +} +} diff --git a/opm/parser/eclipse/Utility/Functional.hpp b/opm/parser/eclipse/Utility/Functional.hpp new file mode 100644 index 000000000..683525e61 --- /dev/null +++ b/opm/parser/eclipse/Utility/Functional.hpp @@ -0,0 +1,214 @@ +#include +#include +#include + +namespace Opm { + +namespace fun { + + /* + * The Utility/Functional library provides convenient high level + * functionality and higher order functions inspiried by functional + * languages (in particular Haskell) and modern C++. The goal is to provide + * lightweight features that reduce boilerplate and make code more + * declarative. + */ + + /* + * map :: (a -> b) -> [a] -> [b] + * + * maps the elements [a] of the passed container C to [b], by using the + * passed function f :: a -> b. Works like map in haskell, lisp, python etc. + * + * C can be any foreach-compatible container (that supports .begin, + * .end), but will always return a vector. + * + * F can be any Callable, that is both function pointer, + * operator()-providing class or std::function, including lambdas. F is + * typically passed by reference. F must be unary of type A (which must + * match what C::const_iterator::operator* returns) and have return + * type B (by value). + * + * In short, this function deal with vector allocation, resizing and + * population based on some function f. + * + * fun::map( f, vec ) is equivalent to: + * vector dst; + * for( auto& x : vec ) dst.push_back( f( x ) ); + * return dst; + * + * The behaviour is undefined if F has any side effects. + * + * -- + * + * int plus1( int x ) { return x + 1; } + * base_vec = { 0, 1, 2, 3, 4 }; + * vec = fun::map( &plus1, base_vec ); + * + * vec => { 1, 2, 3, 4, 5 } + * + * -- + * + * int mul2 = []( int x ) { return x * 2; }; + * base_vec = { 0, 1, 2, 3, 4 }; + * vec = fun::map( mul2, base_vec ); + * + * vec => { 0, 2, 4, 6, 8 }; + * + */ + template< typename F, typename C > + std::vector< typename std::result_of< F( typename C::const_iterator::value_type& ) >::type > + map( F f, const C& src ) { + using A = typename C::const_iterator::value_type; + using B = typename std::result_of< F( A& ) >::type; + std::vector< B > ret; + ret.reserve( src.size() ); + + std::transform( src.begin(), src.end(), std::back_inserter( ret ), f ); + return ret; + } + + /* + * concat :: [[a]] -> [a] + * + * A primitive concat taking a vector of vectors, flattened into a + * single 1 dimensional vector. Moves all the elements so no unecessary + * copies are done. + * + * vec = { { 1 }, { 2, 2 }, { 3, 3, 3 } } + * cvec = concat( vec ) => { 1, 2, 2, 3, 3, 3 } + */ + template< typename A > + std::vector< A > concat( std::vector< std::vector< A > >&& src ) { + const auto size = std::accumulate( src.begin(), src.end(), 0, + []( std::size_t acc, const std::vector< A >& x ) { + return acc + x.size(); + } + ); + + std::vector< A > dst; + dst.reserve( size ); + + for( auto& x : src ) + std::move( x.begin(), x.end(), std::back_inserter( dst ) ); + + return dst; + } + + + /* + * iota :: int -> [int] + * iota :: (int,int) -> [int] + * + * iota (ι) is borrowed from the APL programming language. This particular + * implementation behaves as a generator-like constant-space consecutive + * sequence of integers [m,n). Written to feel similar to std::iota, but as + * a producer instead of straight-up writer. This is similar to python2.7s + * xrange(), python3s range() and haskell's [0..(n-1)]. Some examples + * follow. + * + * Notes: + * * iota defaults to [0,n) + * * iota uses 0 indexing to feel more familiar to C++'s zero indexing. + * * iota can start at negative indices, but will always count upwards. + * * iota::const_iterator does not support operator-- (which would allow + * support for reverse iterators). This can be implemented if need arises. + * * iota is meant to play nice with the rest of fun and to be able to + * replace mundane for loops when the loops only purpose is to create the + * sequence of elements. iota can feel more declarative and work better + * with functions. + * * iota adds value semantics to things that in C++ normally relies on + * variable mutations. iota is meant to make it less painful to write + * immutable and declarative code. + * * as with all iterators, iota( n, m ) behaviour is undefined if m < n + * * unlike python's range, iota doesn't support steps (only increments). + * this is by design to keep this simple and minimal, as well as the name + * iota being somewhat unsuitable for stepping ranges. If the need for + * this arises it will be a separate function. + * + * fun::iota( 5 ) => [ 0, 1, 2, 3, 4 ] + * fun::iota( 3 ) => [ 0, 1, 2 ] + * fun::iota( 1, 6 ) => [ 1, 2, 3, 4, 5 ] + * + * -- + * + * std::vector< int > vec ( 5, 0 ); + * std::iota( vec.begin(), vec.end(), 0 ); + * vec => [ 0, 1, 2, 3, 4 ] + * + * fun::iota i( 5 ); + * std::vector vec( i.begin(), i.end() ); + * vec => [ 0, 1, 2, 3, 4 ] + * + * -- + * + * int plus( int x ) { return x + 1; } + * auto vec = fun::map( &plus, fun::iota( 5 ) ); + * vec => [ 1, 2, 3, 4, 5 ] + * + * is equivalent to + * + * int plus( int x ) { return x + 1; } + * std::vector< int > vec; + * for( int i = 0; i < 5; ++i ) + * vec.push_back( plus( i ) ); + * vec => [ 1, 2, 3, 4, 5 ] + * + * -- + * + * While not the primary intended use case, this enables foreach loop + * syntax over intervals: + * + * for( auto i : fun::iota( 5 ) ) + * std::cout << i << " "; + * + * => 0 1 2 3 4 + * + * for( auto i : fun::iota( 1, 6 ) ) + * std::cout << i << " "; + * + * => 1 2 3 4 5 + * + */ + class iota { + public: + iota( int end ); + iota( int begin, int end ); + + class const_iterator { + public: + using difference_type = int; + using value_type = int; + using pointer = int*; + using reference = int&; + using iterator_category = std::forward_iterator_tag; + + const_iterator() = default; + + int operator*() const; + + const_iterator& operator++(); + const_iterator operator++( int ); + + bool operator==( const const_iterator& rhs ) const; + bool operator!=( const const_iterator& rhs ) const; + + private: + const_iterator( int ); + int value; + + friend class iota; + }; + + size_t size() const; + + const_iterator begin() const; + const_iterator end() const; + + private: + int first; + int last; + }; + +} +} diff --git a/opm/parser/eclipse/Utility/tests/CMakeLists.txt b/opm/parser/eclipse/Utility/tests/CMakeLists.txt new file mode 100644 index 000000000..a9efe2d8b --- /dev/null +++ b/opm/parser/eclipse/Utility/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +foreach(tapp FunctionalTests ) + + opm_add_test(run${tapp} SOURCES ${tapp}.cpp + LIBRARIES opmparser ${Boost_LIBRARIES}) + +endforeach() diff --git a/opm/parser/eclipse/Utility/tests/FunctionalTests.cpp b/opm/parser/eclipse/Utility/tests/FunctionalTests.cpp new file mode 100644 index 000000000..5985804df --- /dev/null +++ b/opm/parser/eclipse/Utility/tests/FunctionalTests.cpp @@ -0,0 +1,70 @@ +#define BOOST_TEST_MODULE FunctionalTests + +#include + +#include + +#include +#include + +using namespace Opm; + +BOOST_AUTO_TEST_CASE(iotaEqualCollections) { + std::vector< int > vec( 5 ); + + for( int i = 0; i < 5; ++i ) + vec[ i ] = i; + + fun::iota iota( 5 ); + for( auto x : iota ) + std::cout << x << " "; + std::cout << std::endl; + std::vector< int > vec_iota( iota.begin(), iota.end() ); + + BOOST_CHECK_EQUAL_COLLECTIONS( + vec_iota.begin(), vec_iota.end(), + vec.begin(), vec.end() ); + BOOST_CHECK_EQUAL_COLLECTIONS( + vec_iota.begin(), vec_iota.end(), + fun::iota( 5 ).begin(), fun::iota( 5 ).end() ); + BOOST_CHECK_EQUAL_COLLECTIONS( + vec.begin(), vec.end(), + fun::iota( 5 ).begin(), fun::iota( 5 ).end() ); +} + +BOOST_AUTO_TEST_CASE(iotaForeach) { + /* this test is mostly a syntax verification test */ + + std::vector< int > vec = { 0, 1, 2, 3, 4 }; + + for( auto x : fun::iota( 5 ) ) + BOOST_CHECK_EQUAL( vec[ x ], x ); +} + +BOOST_AUTO_TEST_CASE(iotaSize) { + BOOST_CHECK_EQUAL( 5, fun::iota( 5 ).size() ); + BOOST_CHECK_EQUAL( 5, fun::iota( 1, 6 ).size() ); + BOOST_CHECK_EQUAL( 0, fun::iota( 0 ).size() ); + BOOST_CHECK_EQUAL( 0, fun::iota( 0, 0 ).size() ); +} + +BOOST_AUTO_TEST_CASE(iotaWithMap) { + const auto plus1 = []( int x ) { return x + 1; }; + + std::vector< int > vec = { 1, 2, 3, 4, 5 }; + auto vec_iota = fun::map( plus1, fun::iota( 5 ) ); + + BOOST_CHECK_EQUAL_COLLECTIONS( + vec_iota.begin(), vec_iota.end(), + vec.begin(), vec.end() ); +} + +BOOST_AUTO_TEST_CASE(iotaNegativeBegin) { + const auto vec = { -4, -3, -2, -1, 0 }; + + fun::iota iota( -4, 1 ); + + BOOST_CHECK_EQUAL_COLLECTIONS( + vec.begin(), vec.end(), + iota.begin(), iota.end() ); +}