diff --git a/CMakeLists.txt b/CMakeLists.txt index b68cb1d..bb1e3a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,8 @@ if ( OVF_BUILD_TEST ) ### Tests add_cxx_test( test_cpp_simple simple.cpp ) add_cxx_test( test_cpp_binary binary.cpp ) + add_cxx_test( test_cpp_atomistic atomistic.cpp ) + add_cxx_test( test_cpp_weird weird_formatting.cpp ) endif() diff --git a/include/detail/atomistic_keywords.hpp b/include/detail/atomistic_keywords.hpp new file mode 100644 index 0000000..e6ca5cd --- /dev/null +++ b/include/detail/atomistic_keywords.hpp @@ -0,0 +1,218 @@ +#pragma once +#ifndef LIBOVF_DETAIL_ATOMISTIC_KEYWORDS_H +#define LIBOVF_DETAIL_ATOMISTIC_KEYWORDS_H +#include "keywords.hpp" + +namespace ovf +{ +namespace detail +{ +namespace keywords +{ + + // set up some utilities for parsing vector3 + namespace Vector3 = ovf::detail::parse::Vector3; + + using vec3_t = float[3]; + + template + using vec3_action_t = Vector3::action; + + template + inline void read_vector(const Input & in, vec3_t & vec3_data) + { + Vector3::read_vec3(in, vec3_data); + } + + ////// meshtype + + // ONLY TRIGGERS ON MESHTYPE LATTICE, the rest of the meshtype keyword is implemented in keywords.hpp + struct meshtype_value_lattice : TAO_PEGTL_ISTRING("lattice") // This keyword is triggered only, when meshtype lattice is found + { }; + + template<> + struct kw_action< meshtype_value_lattice > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.meshtype = strdup(in.string().c_str()); + f._state->found_meshtype = true; + f._state->found_meshtype_lattice = true; + } + }; + + ////// bravaisa + struct bravaisa : TAO_PEGTL_ISTRING("bravaisa") + { }; + + struct bravaisa_value : pegtl::seq {}; + template<> + struct kw_action< bravaisa_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + f._state->found_bravaisa = true; + read_vector(in, segment.bravaisa); + } + }; + + ////// bravaisb + struct bravaisb : TAO_PEGTL_ISTRING("bravaisb") + { }; + + struct bravaisb_value : pegtl::seq {}; + template<> + struct kw_action< bravaisb_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + f._state->found_bravaisb = true; + read_vector(in, segment.bravaisb); + } + }; + + ////// bravaisc + struct bravaisc : TAO_PEGTL_ISTRING("bravaisc") + { }; + + struct bravaisc_value : pegtl::seq {}; + template<> + struct kw_action< bravaisc_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + f._state->found_bravaisc = true; + read_vector(in, segment.bravaisc); + } + }; + + ////// ncellpoints + struct ncellpoints : TAO_PEGTL_ISTRING("ncellpoints") + { }; + + struct ncellpoints_value : numeric_kw_value + { }; + + template<> + struct kw_action< ncellpoints_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.ncellpoints = std::stoi(in.string()); + f._state->found_ncellpoints = true; + f._state->_basis.reserve(segment.ncellpoints); // If we find ncellpoints, we reserve the space + } + }; + + ////// anodes + struct anodes : TAO_PEGTL_ISTRING("anodes") + { }; + + struct anodes_value : numeric_kw_value + { }; + + template<> + struct kw_action< anodes_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.n_cells[0] = std::stoi(in.string()); + f._state->found_anodes = true; + } + }; + + ////// bnodes + struct bnodes : TAO_PEGTL_ISTRING("bnodes") + { }; + + struct bnodes_value : numeric_kw_value + { }; + + template<> + struct kw_action< bnodes_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.n_cells[1] = std::stoi(in.string()); + f._state->found_bnodes = true; + } + }; + + ////// cnodes + struct cnodes : TAO_PEGTL_ISTRING("cnodes") + { }; + + struct cnodes_value : numeric_kw_value + { }; + + template<> + struct kw_action< cnodes_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.n_cells[2] = std::stoi(in.string()); + f._state->found_cnodes = true; + } + }; + + ////// basis + struct basis : TAO_PEGTL_ISTRING("basis") + { }; + + struct cur_basis_line_value_x : pegtl::pad + { }; + + struct cur_basis_line_value_y : pegtl::pad + { }; + + struct cur_basis_line_value_z : pegtl::pad + { }; + + struct basis_value_line : Vector3::vec3 + { }; + + struct basis_value : pegtl::seq< pegtl::eol, pegtl::list, pegtl::opt, basis_value_line>, pegtl::eol > > + { }; + + template<> + struct kw_action< basis_value_line > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + float temp[3]; + read_vector( in, temp ); + f._state->_basis.push_back( {temp[0], temp[1], temp[2]} ); + } + }; + + template<> + struct kw_action + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + f._state->found_basis = true; + // Allocate and data in segment struct and copy + segment.basis = new float[3 * f._state->_basis.size()]; + for( int i=0; i_basis.size(); i++) + { + segment.basis[3*i] = f._state->_basis[i][0]; + segment.basis[3*i + 1] = f._state->_basis[i][1]; + segment.basis[3*i + 2] = f._state->_basis[i][2]; + } + } + }; +} +} +} + +#endif \ No newline at end of file diff --git a/include/detail/keywords.hpp b/include/detail/keywords.hpp new file mode 100644 index 0000000..7d53d39 --- /dev/null +++ b/include/detail/keywords.hpp @@ -0,0 +1,442 @@ +#pragma once +#ifndef LIBOVF_DETAIL_KEYWORDS_H +#define LIBOVF_DETAIL_KEYWORDS_H +#include "ovf.h" +#include "pegtl_defines.hpp" + +#include +#include + +#include +#include + +#include + + +namespace ovf +{ +namespace detail +{ + +namespace keywords +{ + using ovf::detail::parse::decimal_number; + namespace pegtl = tao::pegtl; + + template< typename Rule > + struct kw_action + : pegtl::nothing< Rule > + { }; + + struct end_kw_value : pegtl::at< pegtl::sor> > {}; + struct standard_kw_value : pegtl::until< end_kw_value > {}; + struct numeric_kw_value : pegtl::seq, end_kw_value > {}; + + ////// title + struct title : TAO_PEGTL_ISTRING("title") + { }; + + struct title_value : standard_kw_value + { }; + + template<> + struct kw_action< title_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.title = strdup(in.string().c_str()); + f._state->found_title = true; + } + }; + + ////// desc + struct desc : TAO_PEGTL_ISTRING("desc") + { }; + + struct desc_value : standard_kw_value + { }; + + ////// valuedim + struct valuedim : TAO_PEGTL_ISTRING("valuedim") + { }; + + struct valuedim_value : standard_kw_value + { }; + + template<> + struct kw_action< valuedim_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.valuedim = std::stoi(in.string()); + f._state->found_valuedim = true; + } + }; + + ////// valueunits + struct valueunits : TAO_PEGTL_ISTRING("valueunits") + { }; + + struct valueunits_value : standard_kw_value + { }; + + template<> + struct kw_action< valueunits_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.valueunits = strdup(in.string().c_str()); + f._state->found_valueunits = true; + } + }; + + ////// valuelabels + struct valuelabels : TAO_PEGTL_ISTRING("valuelabels") + { }; + + struct valuelabels_value : standard_kw_value + { }; + + template<> + struct kw_action< valuelabels_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.valuelabels = strdup(in.string().c_str()); + f._state->found_valuelabels = true; + } + }; + + ////// meshtype + struct meshtype : TAO_PEGTL_ISTRING("meshtype") + { }; + + struct meshtype_value : pegtl::sor< TAO_PEGTL_ISTRING("rectangular"), TAO_PEGTL_ISTRING("irregular")> // Only 'rectangular' and 'irregular' allowed + { }; + + template<> + struct kw_action< meshtype_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.meshtype = strdup(in.string().c_str()); + f._state->found_meshtype = true; + } + }; + + ////// meshunit + struct meshunit : TAO_PEGTL_ISTRING("meshunit") + { }; + + struct meshunit_value : standard_kw_value + { }; + + template<> + struct kw_action< meshunit_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.meshunit = strdup(in.string().c_str()); + f._state->found_meshunit = true; + } + }; + + ////// pointcount + struct pointcount : TAO_PEGTL_ISTRING("pointcount") + { }; + + struct pointcount_value : numeric_kw_value + { }; + + template<> + struct kw_action< pointcount_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.pointcount = std::stoi(in.string()); + f._state->found_pointcount = true; + } + }; + + ////// xnodes + struct xnodes : TAO_PEGTL_ISTRING("xnodes") + { }; + + struct xnodes_value : numeric_kw_value + { }; + + template<> + struct kw_action< xnodes_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.n_cells[0] = std::stoi(in.string()); + f._state->found_xnodes = true; + } + }; + + ////// ynodes + struct ynodes : TAO_PEGTL_ISTRING("ynodes") + { }; + + struct ynodes_value : numeric_kw_value + { }; + + template<> + struct kw_action< ynodes_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.n_cells[1] = std::stoi(in.string()); + f._state->found_ynodes = true; + } + }; + + ////// znodes + struct znodes : TAO_PEGTL_ISTRING("znodes") + { }; + + struct znodes_value : numeric_kw_value + { }; + + template<> + struct kw_action< znodes_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.n_cells[2] = std::stoi(in.string()); + f._state->found_znodes = true; + } + }; + + ////// xstepsize + struct xstepsize : TAO_PEGTL_ISTRING("xstepsize") + { }; + + struct xstepsize_value : numeric_kw_value + { }; + + template<> + struct kw_action< xstepsize_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.step_size[0] = std::stof(in.string()); + f._state->found_xstepsize = true; + } + }; + + ////// ystepsize + struct ystepsize : TAO_PEGTL_ISTRING("ystepsize") + { }; + + struct ystepsize_value : numeric_kw_value + { }; + + template<> + struct kw_action< ystepsize_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.step_size[1] = std::stof(in.string()); + f._state->found_ystepsize = true; + } + }; + + ////// zstepsize + struct zstepsize : TAO_PEGTL_ISTRING("zstepsize") + { }; + + struct zstepsize_value : numeric_kw_value + { }; + + template<> + struct kw_action< zstepsize_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.step_size[2] = std::stof(in.string()); + f._state->found_zstepsize = true; + } + }; + + ////// xmin + struct xmin : TAO_PEGTL_ISTRING("xmin") + { }; + + struct xmin_value : numeric_kw_value + { }; + + template<> + struct kw_action< xmin_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.bounds_min[0] = std::stof(in.string()); + f._state->found_xmin = true; + } + }; + + ////// ymin + struct ymin : TAO_PEGTL_ISTRING("ymin") + { }; + + struct ymin_value : numeric_kw_value + { }; + + template<> + struct kw_action< ymin_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.bounds_min[1] = std::stof(in.string()); + f._state->found_ymin = true; + } + }; + + ////// zmin + struct zmin : TAO_PEGTL_ISTRING("zmin") + { }; + + struct zmin_value : numeric_kw_value + { }; + + template<> + struct kw_action< zmin_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.bounds_min[2] = std::stof(in.string()); + f._state->found_zmin = true; + } + }; + + ////// xmax + struct xmax : TAO_PEGTL_ISTRING("xmax") + { }; + + struct xmax_value : numeric_kw_value + { }; + + template<> + struct kw_action< xmax_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.bounds_max[0] = std::stof(in.string()); + f._state->found_xmax = true; + } + }; + + ////// ymax + struct ymax : TAO_PEGTL_ISTRING("ymax") + { }; + + struct ymax_value : numeric_kw_value + { }; + + template<> + struct kw_action< ymax_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.bounds_max[1] = std::stof(in.string()); + f._state->found_ymax = true; + } + }; + + ////// zmax + struct zmax : TAO_PEGTL_ISTRING("zmax") + { }; + + struct zmax_value : numeric_kw_value + { }; + + template<> + struct kw_action< zmax_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.bounds_max[2] = std::stof(in.string()); + f._state->found_zmax = true; + } + }; + + ////// xbase + struct xbase : TAO_PEGTL_ISTRING("xbase") + { }; + + struct xbase_value : numeric_kw_value + { }; + + template<> + struct kw_action< xbase_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.origin[0] = std::stof(in.string()); + f._state->found_xbase = true; + } + }; + + ////// ybase + struct ybase : TAO_PEGTL_ISTRING("ybase") + { }; + + struct ybase_value : numeric_kw_value + { }; + + template<> + struct kw_action< ybase_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.origin[1] = std::stof(in.string()); + f._state->found_ybase = true; + } + }; + + ////// zbase + struct zbase : TAO_PEGTL_ISTRING("zbase") + { }; + + struct zbase_value : numeric_kw_value + { }; + + template<> + struct kw_action< zbase_value > + { + template< typename Input > + static void apply( const Input& in, ovf_file & f, ovf_segment & segment) + { + segment.origin[2] = std::stof(in.string()); + f._state->found_zbase = true; + } + }; + +} +} +} + +#endif \ No newline at end of file diff --git a/include/detail/parse.hpp b/include/detail/parse.hpp index c82f10f..e4dd79c 100644 --- a/include/detail/parse.hpp +++ b/include/detail/parse.hpp @@ -70,10 +70,18 @@ namespace parse if( success ) { success = false; - if( file.version == 2 ) + if(file.ovf_extension_format == OVF_EXTENSION_FORMAT_OVF) { success = pegtl::parse< pegtl::until>>> >( in, file ); - success = pegtl::parse< pegtl::plus, v2::ovf_segment_action >( in, file ); + success = pegtl::parse< pegtl::plus>, v2::ovf_segment_action >( in, file ); + } else if(file.ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF) + { + success = pegtl::parse< pegtl::until>>> >( in, file ); + success = pegtl::parse< pegtl::plus>, v2::ovf_segment_action >( in, file ); + } else if(file.ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF_COMP) + { + success = pegtl::parse< pegtl::until>>> >( in, file ); + success = pegtl::parse< pegtl::plus>, v2::ovf_segment_action >( in, file ); } else if( file.version == 1 ) { @@ -162,26 +170,35 @@ namespace parse file._state->found_ynodes = false; file._state->found_znodes = false; file._state->found_pointcount = false; + file._state->found_bravaisa = false; + file._state->found_bravaisb = false; + file._state->found_bravaisc = false; + file._state->found_ncellpoints = false; + file._state->found_anodes = false; + file._state->found_bnodes = false; + file._state->found_cnodes = false; + file._state->found_basis = false; + + file._state->_cur_basis_line = 0; + + file._state->_basis.resize(0); + bool success = false; - if( file.version == 2 ) + if(file.ovf_extension_format == OVF_EXTENSION_FORMAT_OVF) { - success = pegtl::parse< pegtl::plus, v2::ovf_segment_header_action, v2::ovf_segment_header_control >( in, file, segment ); - } - else if( file.version == 1 ) + success = pegtl::parse< pegtl::plus>, v2::ovf_segment_header_action, v2::ovf_segment_header_control>( in, file, segment ); + } else if(file.ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF) { + success = pegtl::parse< pegtl::plus>, v2::ovf_segment_header_action, v2::ovf_segment_header_control>( in, file, segment ); + } else if(file.ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF_COMP) { - // TODO... - file._state->message_latest = fmt::format( - "libovf segment_header: OVF version \'{}\' in file \'{}\' is not supported...", - file.file_name, file.version); - return OVF_INVALID; - } - else - { - file._state->message_latest = fmt::format( - "libovf segment_header: OVF version \'{}\' in file \'{}\' is not supported...", - file.file_name, file.version); - return OVF_INVALID; + success = pegtl::parse< pegtl::plus>, v2::ovf_segment_header_action, v2::ovf_segment_header_control >( in, file, segment ); + } else { + // TODO... + file._state->message_latest = fmt::format( + "libovf segment_header: OVF version \'{}\' in file \'{}\' is not supported...", + file.file_name, file.version); + return OVF_INVALID; } if( success ) @@ -233,20 +250,35 @@ namespace parse if( file.version == 2 ) { file._state->max_data_index = segment.N*segment.valuedim; - success = pegtl::parse< v2::segment_data, v2::ovf_segment_data_action >( in, file, segment, data ); + success = pegtl::parse< v2::segment_data, v2::ovf_segment_data_action >( in, file, segment, data ); file._state->current_line = 0; file._state->current_column = 0; } else if( file.version == 1 ) { - // TODO... - file._state->message_latest = fmt::format( - "libovf segment_data: OVF version \'{}\' in file \'{}\' is not supported...", - file.file_name, file.version); - return OVF_INVALID; - } - else - { + if(file.ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF) + { + file._state->max_data_index = segment.N*segment.valuedim; + success = pegtl::parse< v2::segment_data, v2::ovf_segment_data_action >( in, file, segment, data ); + file._state->current_line = 0; + file._state->current_column = 0; + } + else if (file.ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF_COMP) + { + file._state->max_data_index = segment.N*segment.valuedim; + success = pegtl::parse< v2::segment_data, v2::ovf_segment_data_action >( in, file, segment, data ); + file._state->current_line = 0; + file._state->current_column = 0; + } + else + { + // TODO... + file._state->message_latest = fmt::format( + "libovf segment_data: OVF version \'{}\' in file \'{}\' is not supported...", + file.file_name, file.version); + return OVF_INVALID; + } + } else { file._state->message_latest = fmt::format( "libovf segment_data: OVF version \'{}\' in file \'{}\' is not supported...", file.file_name, file.version); diff --git a/include/detail/parse_rules.hpp b/include/detail/parse_rules.hpp index 7100bc4..4ff16fd 100644 --- a/include/detail/parse_rules.hpp +++ b/include/detail/parse_rules.hpp @@ -3,6 +3,9 @@ #define LIBOVF_DETAIL_PARSE_RULES_H #include "ovf.h" +#include "keywords.hpp" +#include "atomistic_keywords.hpp" +#include "pegtl_defines.hpp" #include #include @@ -10,54 +13,6 @@ #include -struct parser_state -{ - // For the segment strings - std::vector file_contents{}; - - // for reading data blocks - int current_column = 0; - int current_line = 0; - - std::string keyword="", value=""; - - // Whether certain keywords were found in parsing - bool found_title = false; - bool found_meshunit = false; - bool found_valuedim = false; - bool found_valueunits = false; - bool found_valuelabels = false; - bool found_xmin = false; - bool found_ymin = false; - bool found_zmin = false; - bool found_xmax = false; - bool found_ymax = false; - bool found_zmax = false; - bool found_meshtype = false; - bool found_xbase = false; - bool found_ybase = false; - bool found_zbase = false; - bool found_xstepsize = false; - bool found_ystepsize = false; - bool found_zstepsize = false; - bool found_xnodes = false; - bool found_ynodes = false; - bool found_znodes = false; - bool found_pointcount = false; - - /* - messages, e.g. in case a function returned OVF_ERROR. - message_out will be filled and returned by ovf_latest_message, while message_latest - will be filled by other functions and cleared by ovf_latest_message. - */ - std::string message_out="", message_latest=""; - - int max_data_index=0; - int tmp_idx=0; - std::array tmp_vec3 = std::array{0,0,0}; - - std::ios::pos_type n_segments_pos = 0; -}; namespace ovf { @@ -72,6 +27,16 @@ namespace parse : pegtl::string< '#' > {}; + // "%" + struct magic_char + : pegtl::string< '%' > + {}; + + // "##% " + struct magic_prefix + : pegtl::seq< pegtl::string< '#', '#'>, magic_char > + {}; + // "#\eol" struct empty_line : pegtl::seq< pegtl::string< '#' >, pegtl::star > @@ -82,14 +47,22 @@ namespace parse : pegtl::range< '1', '2' > {}; + struct version_string + : pegtl::sor< TAO_PEGTL_ISTRING("OOMMF OVF"), TAO_PEGTL_ISTRING("AOVF_COMP"), TAO_PEGTL_ISTRING("AOVF") > + {}; + // " OOMMF OVF " struct version - : pegtl::seq< prefix, pegtl::pad< TAO_PEGTL_ISTRING("OOMMF OVF"), pegtl::blank >, version_number, pegtl::until > + : + pegtl::sor< + pegtl::seq< prefix, pegtl::pad< TAO_PEGTL_ISTRING("OOMMF OVF"), pegtl::blank>, pegtl::until, magic_prefix, pegtl::pad< version_string, pegtl::blank>, version_number, pegtl::until >, + pegtl::seq< prefix, pegtl::pad< version_string, pegtl::blank >, version_number, pegtl::until > + > {}; - // " Segment count: " + // " Segment count: " struct segment_count_number - : pegtl::plus + : pegtl::pad, pegtl::blank> {}; // " Segment count: " @@ -124,6 +97,28 @@ namespace parse } }; + template<> + struct ovf_file_action< version_string > + { + template< typename Input > + static void apply( const Input& in, ovf_file & file ) + { + file.version_string = strdup(in.string().c_str()); + if(in.string() == "AOVF_COMP") + { + file.ovf_extension_format = OVF_EXTENSION_FORMAT_AOVF_COMP; + } else if(in.string() == "AOVF") + { + file.ovf_extension_format = OVF_EXTENSION_FORMAT_AOVF; + } else if(in.string() == "OOMMF OVF") + { + file.ovf_extension_format = OVF_EXTENSION_FORMAT_OVF; + } else { + throw pegtl::parse_error(fmt::format("Detected invalid version string {}", in.string()), in); + } + } + }; + template<> struct ovf_file_action< segment_count_number > { @@ -136,21 +131,37 @@ namespace parse }; ////////////////////////// - namespace v2 { + enum Version + { + OVF2 = OVF_EXTENSION_FORMAT_OVF, + AOVF = OVF_EXTENSION_FORMAT_AOVF, + CAOVF = OVF_EXTENSION_FORMAT_AOVF_COMP + }; + // "# " struct prefix : pegtl::string< '#' > {}; + template + struct comment_prefix + : pegtl::string< '#', '#'> + {}; + + template<> + struct comment_prefix + : pegtl::seq, pegtl::not_at> + {}; // Line without contents, up to EOL or comment + template struct empty_line : pegtl::seq< - pegtl::string<'#'>, + prefix, pegtl::until< pegtl::at< - pegtl::sor> + pegtl::sor> >, pegtl::blank > @@ -158,9 +169,11 @@ namespace parse {}; // Comment line up to EOL. "##" initiates comment line + // We have to template here so we can specialize for the ##% prefix later + template struct comment : pegtl::seq< - pegtl::string< '#', '#' >, + comment_prefix, pegtl::until< pegtl::at, pegtl::any @@ -169,18 +182,14 @@ namespace parse {}; // Number of lines without content (empty and comment lines) + template struct skippable_lines : pegtl::star< pegtl::sor< - pegtl::seq< empty_line, pegtl::opt, pegtl::eol >, - pegtl::seq< comment, pegtl::eol > + pegtl::seq< empty_line, pegtl::opt>, pegtl::eol >, + pegtl::seq< comment, pegtl::eol > > > {}; - // " OOMMF OVF " - struct version - : pegtl::seq< prefix, pegtl::pad< TAO_PEGTL_ISTRING("OOMMF OVF"), pegtl::blank >, pegtl::range< '1', '2' >, pegtl::until > - {}; - // " Segment count: " struct segment_count_number : pegtl::plus @@ -188,7 +197,7 @@ namespace parse // " Segment count: " struct segment_count - : pegtl::seq< prefix, pegtl::pad< TAO_PEGTL_ISTRING("Segment count:"), pegtl::blank >, segment_count_number, pegtl::eol > + : pegtl::seq< prefix, pegtl::pad< TAO_PEGTL_ISTRING("Segment count:"), pegtl::blank >, pegtl::pad, pegtl::eol > {}; // " Begin: " @@ -203,49 +212,6 @@ namespace parse ////////////////////////////////////////////// - struct opt_plus_minus - : pegtl::opt< pegtl::one< '+', '-' > > - {}; - - struct inf - : pegtl::seq< - pegtl::istring< 'i', 'n', 'f' >, - pegtl::opt< pegtl::istring< 'i', 'n', 'i', 't', 'y' > > > - {}; - - struct nan - : pegtl::seq< - pegtl::istring< 'n', 'a', 'n' >, - pegtl::opt< pegtl::one< '(' >, - pegtl::plus< pegtl::alnum >, - pegtl::one< ')' > > > - {}; - - template< typename D > - struct basic_number - : pegtl::if_then_else< - pegtl::one< '.' >, - pegtl::plus< D >, - pegtl::seq< - pegtl::plus< D >, - pegtl::opt< pegtl::one< '.' > >, - pegtl::star< D > - > - > - {}; - - struct exponent - : pegtl::seq< - opt_plus_minus, - pegtl::plus< pegtl::digit > > - {}; - - struct decimal_number - : pegtl::seq< - basic_number< pegtl::digit >, - pegtl::opt< pegtl::one< 'e', 'E' >, exponent > > - {}; - struct hexadecimal_number // TODO: is this actually hexadecimal?? : pegtl::seq< pegtl::one< '0' >, @@ -306,7 +272,7 @@ namespace parse // This is how a line ends: either eol or the begin of a comment struct line_end - : pegtl::sor> + : pegtl::sor>> {}; // This checks that the line end is met and moves up until eol @@ -316,32 +282,105 @@ namespace parse ////////////////////////////////////////////// - struct keyword - : pegtl::until< pegtl::at, - pegtl::if_must, pegtl::any> >//, pegtl::not_at> > + template + struct keyword_value_pair + : pegtl::seq< + prefix, + pegtl::pad< kw, pegtl::blank >, + TAO_PEGTL_ISTRING(":"), + pegtl::pad< val, pegtl::blank >, + pegtl::until>, + finish_line > {}; - struct value : pegtl::seq< pegtl::until> > {}; - - struct keyword_value_line + template + struct magic_keyword_value_pair : pegtl::seq< - prefix, - pegtl::pad< keyword, pegtl::blank >, + magic_prefix, + pegtl::pad< kw, pegtl::blank >, TAO_PEGTL_ISTRING(":"), - pegtl::pad< value, pegtl::blank >, + pegtl::pad< val, pegtl::blank >, + pegtl::until>, finish_line > {}; - // + template + struct keyword_value_line : pegtl::not_at + {}; + + template<> + struct keyword_value_line + : pegtl::sor< + keyword_value_pair< keywords::title, keywords::title_value >, + keyword_value_pair< keywords::desc, keywords::desc_value >, + keyword_value_pair< keywords::valuedim, keywords::valuedim_value >, + keyword_value_pair< keywords::valueunits, keywords::valueunits_value >, + keyword_value_pair< keywords::valuelabels, keywords::valuelabels_value >, + keyword_value_pair< keywords::meshtype, keywords::meshtype_value >, + keyword_value_pair< keywords::meshunit, keywords::meshunit_value >, + keyword_value_pair< keywords::pointcount, keywords::pointcount_value >, + keyword_value_pair< keywords::xnodes, keywords::xnodes_value >, + keyword_value_pair< keywords::ynodes, keywords::ynodes_value >, + keyword_value_pair< keywords::znodes, keywords::znodes_value >, + keyword_value_pair< keywords::xstepsize, keywords::xstepsize_value >, + keyword_value_pair< keywords::ystepsize, keywords::ystepsize_value >, + keyword_value_pair< keywords::zstepsize, keywords::zstepsize_value >, + keyword_value_pair< keywords::xmin, keywords::xmin_value >, + keyword_value_pair< keywords::ymin, keywords::ymin_value >, + keyword_value_pair< keywords::zmin, keywords::zmin_value >, + keyword_value_pair< keywords::xmax, keywords::xmax_value >, + keyword_value_pair< keywords::ymax, keywords::ymax_value >, + keyword_value_pair< keywords::zmax, keywords::zmax_value >, + keyword_value_pair< keywords::xbase, keywords::xbase_value >, + keyword_value_pair< keywords::ybase, keywords::ybase_value >, + keyword_value_pair< keywords::zbase, keywords::zbase_value > + > + {}; + + template<> + struct keyword_value_line + : pegtl::sor< + keyword_value_line, + // Atomistic extension + keyword_value_pair< keywords::meshtype, keywords::meshtype_value_lattice >, + keyword_value_pair< keywords::anodes, keywords::anodes_value >, + keyword_value_pair< keywords::bnodes, keywords::bnodes_value >, + keyword_value_pair< keywords::cnodes, keywords::cnodes_value >, + keyword_value_pair< keywords::bravaisa, keywords::bravaisa_value >, + keyword_value_pair< keywords::bravaisb, keywords::bravaisb_value >, + keyword_value_pair< keywords::bravaisc, keywords::bravaisc_value >, + keyword_value_pair< keywords::ncellpoints, keywords::ncellpoints_value >, + keyword_value_pair< keywords::basis, keywords::basis_value > + > + {}; + + template<> + struct keyword_value_line + : pegtl::sor< + keyword_value_line, + // Atomistic extension + magic_keyword_value_pair< keywords::meshtype, keywords::meshtype_value_lattice >, + magic_keyword_value_pair< keywords::anodes, keywords::anodes_value >, + magic_keyword_value_pair< keywords::bnodes, keywords::bnodes_value >, + magic_keyword_value_pair< keywords::cnodes, keywords::cnodes_value >, + magic_keyword_value_pair< keywords::bravaisa, keywords::bravaisa_value >, + magic_keyword_value_pair< keywords::bravaisb, keywords::bravaisb_value >, + magic_keyword_value_pair< keywords::bravaisc, keywords::bravaisc_value >, + magic_keyword_value_pair< keywords::ncellpoints, keywords::ncellpoints_value >, + magic_keyword_value_pair< keywords::basis, keywords::basis_value > + > + {}; + + template struct header : pegtl::seq< - begin, TAO_PEGTL_ISTRING("Header"), finish_line, + begin, pegtl::pad, finish_line, pegtl::until< - pegtl::seq, + pegtl::seq>, pegtl::must< - skippable_lines, - keyword_value_line, - skippable_lines + skippable_lines, + keyword_value_line, + skippable_lines > >, finish_line @@ -349,21 +388,22 @@ namespace parse {}; // + template struct segment : pegtl::seq< - pegtl::star>, - pegtl::seq< begin, TAO_PEGTL_ISTRING("Segment"), pegtl::eol>, - pegtl::until>, pegtl::eol > + pegtl::star, pegtl::eol>>, + pegtl::seq< begin, pegtl::pad, finish_line>, + pegtl::until>>, finish_line > {}; // Class template for user-defined actions that does nothing by default. - template< typename Rule > + template< typename Rule> struct ovf_segment_action : pegtl::nothing< Rule > {}; - template<> - struct ovf_segment_action< segment > + template + struct ovf_segment_action< segment > { template< typename Input > static void apply( const Input& in, ovf_file & file ) @@ -375,30 +415,33 @@ namespace parse ////////////////////////////////////////////// // + template struct segment_header : pegtl::seq< - pegtl::star>, - pegtl::seq< begin, TAO_PEGTL_ISTRING("Segment"), pegtl::eol>, - pegtl::star>, - header, - pegtl::star>, - pegtl::until>, pegtl::eol > + skippable_lines, + pegtl::seq< begin, pegtl::pad, pegtl::eol>, + skippable_lines, + header, + skippable_lines, + pegtl::until>>, pegtl::eol > {}; // Class template for user-defined actions that does nothing by default. template< typename Rule > - struct ovf_segment_header_action - : pegtl::nothing< Rule > + struct ovf_segment_header_action : keywords::kw_action {}; - template<> - struct ovf_segment_header_action< segment_header > + template + struct ovf_segment_header_action< segment_header > { template< typename Input > static void apply( const Input& in, ovf_file & file, ovf_segment & segment ) { + // Check if all required keywords were present std::vector missing_keywords(0); + std::vector wrong_keywords(0); + if( !file._state->found_title ) missing_keywords.push_back("title"); if( !file._state->found_meshunit ) @@ -422,10 +465,9 @@ namespace parse if( !file._state->found_meshtype ) missing_keywords.push_back("meshtype"); - if( std::string(segment.meshtype) == "rectangular" ) + if( std::string(segment.meshtype) == "rectangular" || (file.ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF_COMP && file._state->found_meshtype_lattice) ) { segment.N = segment.n_cells[0] * segment.n_cells[1] * segment.n_cells[2]; - if( !file._state->found_xbase ) missing_keywords.push_back("xbase"); if( !file._state->found_ybase ) @@ -444,22 +486,95 @@ namespace parse missing_keywords.push_back("ynodes"); if( !file._state->found_znodes ) missing_keywords.push_back("znodes"); + } else { + if( file._state->found_xbase ) + wrong_keywords.push_back("xbase"); + if( file._state->found_ybase ) + wrong_keywords.push_back("ybase"); + if( file._state->found_zbase ) + wrong_keywords.push_back("zbase"); + if( file._state->found_xstepsize ) + wrong_keywords.push_back("xstepsize"); + if( file._state->found_ystepsize ) + wrong_keywords.push_back("ystepsize"); + if( file._state->found_zstepsize ) + wrong_keywords.push_back("zstepsize"); + if( file._state->found_xnodes ) + wrong_keywords.push_back("xnodes"); + if( file._state->found_ynodes ) + wrong_keywords.push_back("ynodes"); + if( file._state->found_znodes ) + wrong_keywords.push_back("znodes"); } - else if( std::string(segment.meshtype) == "irregular" ) + + if( std::string(segment.meshtype) == "irregular" ) { segment.N = segment.pointcount; - if( !file._state->found_pointcount ) missing_keywords.push_back("pointcount"); + } else { + if( file._state->found_pointcount ) + wrong_keywords.push_back("pointcount"); + } + + if( std::string(segment.meshtype) == "lattice" || file._state->found_meshtype_lattice ) + { + segment.N = segment.n_cells[0] * segment.n_cells[1] * segment.n_cells[2] * segment.ncellpoints; + if( !file._state->found_anodes ) + missing_keywords.push_back("anodes"); + if( !file._state->found_bnodes ) + missing_keywords.push_back("bnodes"); + if( !file._state->found_cnodes ) + missing_keywords.push_back("cnodes"); + if( !file._state->found_ncellpoints ) + missing_keywords.push_back("ncellpoints"); + if( !file._state->found_basis ) + missing_keywords.push_back("basis"); + if( !file._state->found_bravaisa ) + missing_keywords.push_back("bravaisa"); + if( !file._state->found_bravaisb ) + missing_keywords.push_back("bravaisb"); + if( !file._state->found_bravaisc ) + missing_keywords.push_back("bravaisc"); + + if( segment.ncellpoints != file._state->_basis.size() ) + throw tao::pegtl::parse_error( fmt::format("ncellpoints ({}) and number of specified basis atoms ({}) does not match!", segment.ncellpoints, file._state->_basis.size() ), in); + + } else { + if( file._state->found_anodes ) + wrong_keywords.push_back("anodes"); + if( file._state->found_bnodes ) + wrong_keywords.push_back("bnodes"); + if( file._state->found_cnodes ) + wrong_keywords.push_back("cnodes"); + if( file._state->found_ncellpoints ) + wrong_keywords.push_back("ncellpoints"); + if( file._state->found_basis ) + wrong_keywords.push_back("basis"); + if( file._state->found_bravaisa ) + wrong_keywords.push_back("bravaisa"); + if( file._state->found_bravaisb ) + wrong_keywords.push_back("bravaisb"); + if( file._state->found_bravaisc ) + wrong_keywords.push_back("bravaisc"); } if( missing_keywords.size() > 0 ) { - std::string message = fmt::format( "Missing keywords: \"{}\"", missing_keywords[0] ); + std::string message = fmt::format( "Missing keywords for meshtype \"{}\": \"{}\"", segment.meshtype, missing_keywords[0] ); for( int i=1; i < missing_keywords.size(); ++i ) message += fmt::format( ", \"{}\"", missing_keywords[i] ); throw tao::pegtl::parse_error( message, in ); } + + if( wrong_keywords.size() > 0 ) + { + std::string message = fmt::format( "Wrong keywords for meshtype \"{}\": \"{}\"", segment.meshtype, wrong_keywords[0] ); + for( int i=1; i < wrong_keywords.size(); ++i ) + message += fmt::format( ", \"{}\"", wrong_keywords[i] ); + throw tao::pegtl::parse_error( message, in ); + } + } }; @@ -475,215 +590,6 @@ namespace parse } }; - //////////////////////////////////////////////////////////////////////////////////////////// - - template<> - struct ovf_segment_header_action< keyword > - { - template< typename Input > - static void apply( const Input& in, ovf_file & f, ovf_segment & segment ) - { - f._state->keyword = in.string(); - std::transform(f._state->keyword.begin(), f._state->keyword.end(),f._state->keyword.begin(), ::tolower); - } - }; - - template<> - struct ovf_segment_header_action< value > - { - template< typename Input > - static void apply( const Input& in, ovf_file & f, ovf_segment & segment ) - { - f._state->value = in.string(); - } - }; - - template<> - struct ovf_segment_header_action< keyword_value_line > - { - template< typename Input > - static void apply( const Input& in, ovf_file & f, ovf_segment & segment ) - { - if( f._state->keyword == "title" ) - { - segment.title = strdup(f._state->value.c_str()); - f._state->found_title = true; - } - else if( f._state->keyword == "desc" ) - segment.comment = strdup(f._state->value.c_str()); - else if( f._state->keyword == "meshunit" ) - { - segment.meshunit = strdup(f._state->value.c_str()); - f._state->found_meshunit = true; - } - else if( f._state->keyword == "valuedim" ) - { - segment.valuedim = std::stoi(f._state->value.c_str()); - f._state->found_valuedim = true; - } - else if( f._state->keyword == "valueunits" ) - { - segment.valueunits = strdup(f._state->value.c_str()); - f._state->found_valueunits = true; - } - else if( f._state->keyword == "valuelabels" ) - { - segment.valuelabels = strdup(f._state->value.c_str()); - f._state->found_valuelabels = true; - } - else if( f._state->keyword == "xmin" ) - { - segment.bounds_min[0] = std::stof(f._state->value.c_str()); - f._state->found_xmin = true; - } - else if( f._state->keyword == "ymin" ) - { - segment.bounds_min[1] = std::stof(f._state->value.c_str()); - f._state->found_ymin = true; - } - else if( f._state->keyword == "zmin" ) - { - segment.bounds_min[2] = std::stof(f._state->value.c_str()); - f._state->found_zmin = true; - } - else if( f._state->keyword == "xmax" ) - { - segment.bounds_max[0] = std::stof(f._state->value.c_str()); - f._state->found_xmax = true; - } - else if( f._state->keyword == "ymax" ) - { - segment.bounds_max[1] = std::stof(f._state->value.c_str()); - f._state->found_ymax = true; - } - else if( f._state->keyword == "zmax" ) - { - segment.bounds_max[2] = std::stof(f._state->value.c_str()); - f._state->found_zmax = true; - } - else if( f._state->keyword == "meshtype" ) - { - std::string meshtype = f._state->value; - std::transform(meshtype.begin(), meshtype.end(), meshtype.begin(), ::tolower); - if( std::string(segment.meshtype) == "" ) - { - if( meshtype != "rectangular" && meshtype != "irregular" ) - throw tao::pegtl::parse_error( fmt::format( - "Invalid meshtype: \"{}\"", meshtype), in ); - segment.meshtype = strdup(meshtype.c_str()); - } - else if( std::string(segment.meshtype) != meshtype ) - { - throw tao::pegtl::parse_error( fmt::format( - "meshtype \"{}\" was specified, but due to other parameters specified before, \"{}\" was expected!", - meshtype, segment.meshtype), in ); - } - f._state->found_meshtype = true; - } - else if( f._state->keyword == "xbase" ) - { - if( std::string(segment.meshtype) != "rectangular" ) - throw tao::pegtl::parse_error( fmt::format( - "xbase is only for rectangular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("rectangular"); - segment.origin[0] = std::stof(f._state->value.c_str()); - f._state->found_xbase = true; - } - else if( f._state->keyword == "ybase" ) - { - if( std::string(segment.meshtype) != "rectangular" ) - throw tao::pegtl::parse_error( fmt::format( - "ybase is only for rectangular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("rectangular"); - segment.origin[1] = std::stof(f._state->value.c_str()); - f._state->found_ybase = true; - } - else if( f._state->keyword == "zbase" ) - { - if( std::string(segment.meshtype) != "rectangular" ) - throw tao::pegtl::parse_error( fmt::format( - "zbase is only for rectangular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("rectangular"); - segment.origin[2] = std::stof(f._state->value.c_str()); - f._state->found_zbase = true; - } - else if( f._state->keyword == "xstepsize" ) - { - if( std::string(segment.meshtype) != "rectangular" ) - throw tao::pegtl::parse_error( fmt::format( - "xstepsize is only for rectangular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("rectangular"); - segment.step_size[0] = std::stof(f._state->value.c_str()); - f._state->found_xstepsize = true; - } - else if( f._state->keyword == "ystepsize" ) - { - if( std::string(segment.meshtype) != "rectangular" ) - throw tao::pegtl::parse_error( fmt::format( - "ystepsize is only for rectangular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("rectangular"); - segment.step_size[1] = std::stof(f._state->value.c_str()); - f._state->found_ystepsize = true; - } - else if( f._state->keyword == "zstepsize" ) - { - if( std::string(segment.meshtype) != "rectangular" ) - throw tao::pegtl::parse_error( fmt::format( - "zstepsize is only for rectangular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("rectangular"); - segment.step_size[2] = std::stof(f._state->value.c_str()); - f._state->found_zstepsize = true; - } - else if( f._state->keyword == "xnodes" ) - { - if( std::string(segment.meshtype) != "rectangular" ) - throw tao::pegtl::parse_error( fmt::format( - "xnodes is only for rectangular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("rectangular"); - segment.n_cells[0] = std::stoi(f._state->value.c_str()); - f._state->found_xnodes = true; - } - else if( f._state->keyword == "ynodes" ) - { - if( std::string(segment.meshtype) != "rectangular" ) - throw tao::pegtl::parse_error( fmt::format( - "ynodes is only for rectangular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("rectangular"); - segment.n_cells[1] = std::stoi(f._state->value.c_str()); - f._state->found_ynodes = true; - } - else if( f._state->keyword == "znodes" ) - { - if( std::string(segment.meshtype) != "rectangular" ) - throw tao::pegtl::parse_error( fmt::format( - "znodes is only for rectangular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("rectangular"); - segment.n_cells[2] = std::stoi(f._state->value.c_str()); - f._state->found_znodes = true; - } - else if( f._state->keyword == "pointcount" ) - { - if( std::string(segment.meshtype) != "" && std::string(segment.meshtype) != "irregular" ) - throw tao::pegtl::parse_error( fmt::format( - "pointcount is only for irregular meshes! Mesh type is \"{}\"", segment.meshtype), in ); - segment.meshtype = strdup("irregular"); - segment.pointcount = std::stoi(f._state->value.c_str()); - f._state->found_pointcount = true; - } - else - { - // UNKNOWN KEYWORD - throw tao::pegtl::parse_error( fmt::format( - "unknown keyword \"{}\": \"{}\"", f._state->keyword, f._state->value), in ); - } - - f._state->keyword = ""; - f._state->value = ""; - } - }; - - //////////////////////////////////////////////////////////////////////////////////////////// - struct data_text : pegtl::seq< begin, TAO_PEGTL_ISTRING("Data Text"), pegtl::eol, @@ -718,16 +624,17 @@ namespace parse > {}; + template struct segment_data : pegtl::seq< - pegtl::star>, - begin, TAO_PEGTL_ISTRING("Segment"), pegtl::eol, - pegtl::star>, - header, - pegtl::star>, + pegtl::star, pegtl::eol>>, + begin, pegtl::pad, pegtl::eol, + pegtl::star, pegtl::eol>>, + header, + pegtl::star, pegtl::eol>>, pegtl::sor< data_text, data_csv, data_binary_4, data_binary_8 >, - pegtl::star>, - pegtl::until>, pegtl::eol > + pegtl::star, pegtl::eol>>, + pegtl::until>>, pegtl::eol > {}; //////////////////////////////////// @@ -929,7 +836,19 @@ namespace parse }; template<> template< typename Input, typename... States > - void ovf_segment_header_control< keyword_value_line >::raise( const Input& in, States&&... ) + void ovf_segment_header_control>::raise( const Input& in, States&&... ) + { + throw keyword_value_line_error( in ); + } + + template<> template< typename Input, typename... States > + void ovf_segment_header_control>::raise( const Input& in, States&&... ) + { + throw keyword_value_line_error( in ); + } + + template<> template< typename Input, typename... States > + void ovf_segment_header_control>::raise( const Input& in, States&&... ) { throw keyword_value_line_error( in ); } diff --git a/include/detail/pegtl_defines.hpp b/include/detail/pegtl_defines.hpp new file mode 100644 index 0000000..37dc5c3 --- /dev/null +++ b/include/detail/pegtl_defines.hpp @@ -0,0 +1,200 @@ +#pragma once +#ifndef LIBOVF_DETAIL_PEGTL_DEFINES_H +#define LIBOVF_DETAIL_PEGTL_DEFINES_H + +#include +#include +#include +#include +#include +#include + +struct parser_state +{ + // For the segment strings + std::vector file_contents{}; + + // for reading data blocks + int current_column = 0; + int current_line = 0; + + // Whether certain keywords were found in parsing + bool found_title = false; + bool found_meshunit = false; + bool found_valuedim = false; + bool found_valueunits = false; + bool found_valuelabels = false; + bool found_xmin = false; + bool found_ymin = false; + bool found_zmin = false; + bool found_xmax = false; + bool found_ymax = false; + bool found_zmax = false; + bool found_meshtype = false; + bool found_xbase = false; + bool found_ybase = false; + bool found_zbase = false; + bool found_xstepsize = false; + bool found_ystepsize = false; + bool found_zstepsize = false; + bool found_xnodes = false; + bool found_ynodes = false; + bool found_znodes = false; + bool found_pointcount = false; + bool found_bravaisa = false; + bool found_bravaisb = false; + bool found_bravaisc = false; + bool found_ncellpoints = false; + bool found_anodes = false; + bool found_bnodes = false; + bool found_cnodes = false; + bool found_basis = false; + + /* + We need and additional bool, because in the compatibiliby format, we can have: + # meshtype : rectangular + ##% meshtype : lattice + So if the meshtype is rectangula, but found_meshtype_lattice is true we know the lattice meshtype was requested in the CAOVF format + */ + bool found_meshtype_lattice = false; + + std::vector> _basis = std::vector>(0); + int _cur_basis_line = 0; + + /* + messages, e.g. in case a function returned OVF_ERROR. + message_out will be filled and returned by ovf_latest_message, while message_latest + will be filled by other functions and cleared by ovf_latest_message. + */ + std::string message_out="", message_latest=""; + + int max_data_index=0; + int tmp_idx=0; + std::array tmp_vec3 = std::array{0,0,0}; + + std::ios::pos_type n_segments_pos = 0; +}; + +namespace ovf +{ +namespace detail +{ +namespace parse +{ + + namespace pegtl = tao::pegtl; + + struct opt_plus_minus + : pegtl::opt< pegtl::one< '+', '-' > > + {}; + + struct inf + : pegtl::seq< + pegtl::istring< 'i', 'n', 'f' >, + pegtl::opt< pegtl::istring< 'i', 'n', 'i', 't', 'y' > > > + {}; + + struct nan + : pegtl::seq< + pegtl::istring< 'n', 'a', 'n' >, + pegtl::opt< pegtl::one< '(' >, + pegtl::plus< pegtl::alnum >, + pegtl::one< ')' > > > + {}; + + template< typename D > + struct basic_number + : pegtl::if_then_else< + pegtl::one< '.' >, + pegtl::plus< D >, + pegtl::seq< + pegtl::plus< D >, + pegtl::opt< pegtl::one< '.' > >, + pegtl::star< D > + > + > + {}; + + struct exponent + : pegtl::seq< + opt_plus_minus, + pegtl::plus< pegtl::digit > > + {}; + + struct decimal_number + : pegtl::seq< + basic_number< pegtl::digit >, + pegtl::opt< pegtl::one< 'e', 'E' >, exponent > > + {}; + + struct hexadecimal_number // TODO: is this actually hexadecimal?? + : pegtl::seq< + pegtl::one< '0' >, + pegtl::one< 'x', 'X' >, + basic_number< pegtl::xdigit >, + pegtl::opt< pegtl::one< 'p', 'P' >, exponent > > + {}; + + struct float_value + : pegtl::seq< + opt_plus_minus, + decimal_number > + {}; + + namespace Vector3 + { + struct x_val : tao::pegtl::pad {}; + struct y_val : tao::pegtl::pad {}; + struct z_val : tao::pegtl::pad {}; + struct vec3 : tao::pegtl::seq {}; + + template + struct action + : pegtl::nothing< Rule > + { }; + + template + struct action< x_val, vec3_t> + { + template< typename Input > + static void apply( const Input& in, vec3_t & data) + { + data[0] = std::stof(in.string()); + } + }; + + template + struct action< y_val, vec3_t > + { + template< typename Input > + static void apply( const Input& in, vec3_t & data ) + { + data[1] = std::stof(in.string()); + } + }; + + template + struct action + { + template< typename Input > + static void apply( const Input& in, vec3_t & data) + { + data[2] = std::stof(in.string()); + } + }; + + template class action_t> + inline void read_vec3(input_t & in, vec3_t & vec_data) + { + std::string in_str = std::string(in.string()); + pegtl::memory_input in_mem( in_str, "" ); + bool success = pegtl::parse>, action_t>(in_mem, vec_data); + } + + } + +} +} +} + +#endif \ No newline at end of file diff --git a/include/detail/write.hpp b/include/detail/write.hpp index 948b4a6..1afaa1d 100644 --- a/include/detail/write.hpp +++ b/include/detail/write.hpp @@ -72,9 +72,18 @@ namespace write } - inline std::string top_header_string() + inline std::string top_header_string(int ovf_extension_format) { std::string ret = "# OOMMF OVF 2.0\n"; + + if(ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF_COMP) + { + ret += "##% AOVF_COMP 1.0\n"; + } + if(ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF) + { + ret = "# AOVF 1.0\n"; + } ret += empty_line; // create padding string @@ -260,12 +269,14 @@ namespace write // Type of mesh and further keywords depending on it std::string meshtype = segment->meshtype; - if( meshtype == "" ) + + if( meshtype == "" || (meshtype == "lattice" && file->ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF_COMP) ) // In compatibility mode we cannot use meshtype 'lattice' meshtype = "rectangular"; + output_to_file += fmt::format( "# meshtype: {}\n", meshtype ); int n_rows = 0; - if( meshtype == "rectangular" ) + if( meshtype == "rectangular" && std::string(segment->meshtype) != "lattice") { // Latice origin in space output_to_file += fmt::format( "# xbase: {}\n", segment->origin[0] ); @@ -284,10 +295,60 @@ namespace write n_rows = segment->n_cells[0]*segment->n_cells[1]*segment->n_cells[2]; } - else if( std::string(segment->meshtype) == "irregular" ) + else if( meshtype == "irregular" ) { output_to_file += fmt::format( "# pointcount: {}\n", segment->pointcount ); n_rows = segment->pointcount; + } else if( std::string(segment->meshtype) == "lattice" ) + { + std::string prefix = "#"; + if(file->ovf_extension_format == OVF_EXTENSION_FORMAT_AOVF_COMP) + { + prefix = "##%"; + + // In compatibility mode we have to add the required fields for the rectangular mesh + // Latice origin in space + output_to_file += fmt::format( "# xbase: {}\n", segment->origin[0] ); + output_to_file += fmt::format( "# ybase: {}\n", segment->origin[1] ); + output_to_file += fmt::format( "# zbase: {}\n", segment->origin[2] ); + + // Mesh spacing + output_to_file += fmt::format( "# xstepsize: {}\n", segment->step_size[0] ); + output_to_file += fmt::format( "# ystepsize: {}\n", segment->step_size[1] ); + output_to_file += fmt::format( "# zstepsize: {}\n", segment->step_size[2] ); + + // Number of nodes along each direction + output_to_file += fmt::format( "# xnodes: {}\n", segment->n_cells[0] * segment->ncellpoints ); // We fold the basis atoms into xnodes + output_to_file += fmt::format( "# ynodes: {}\n", segment->n_cells[1] ); + output_to_file += fmt::format( "# znodes: {}\n", segment->n_cells[2] ); + + // We also add the lattice meshtype as a magic comment + output_to_file += fmt::format( "#\n" ); + output_to_file += fmt::format( "##% meshtype: {}\n", "lattice" ); + + } if(file->ovf_extension_format == OVF_EXTENSION_FORMAT_OVF) + { + file->_state->message_latest = fmt::format( + "Not writing out any data to file \"{}\", because meshtype is invalid: \"{}\". " + "To enable the `lattice` meshtype, set ovf_extension_format to OVF_EXTENSION_FORMAT_AOVF_COMP or OVF_EXTENSION_FORMAT_OVf.", + file->file_name, segment->meshtype); + return OVF_ERROR; + } + + output_to_file += fmt::format( "{} anodes: {}\n", prefix, segment->n_cells[0] ); + output_to_file += fmt::format( "{} bnodes: {}\n", prefix, segment->n_cells[1] ); + output_to_file += fmt::format( "{} cnodes: {}\n", prefix, segment->n_cells[2] ); + output_to_file += fmt::format( "{} bravaisa: {:22.12f} {:22.12f} {:22.12f}\n", prefix, segment->bravaisa[0], segment->bravaisa[1], segment->bravaisa[2] ); + output_to_file += fmt::format( "{} bravaisb: {:22.12f} {:22.12f} {:22.12f}\n", prefix, segment->bravaisb[0], segment->bravaisb[1], segment->bravaisb[2] ); + output_to_file += fmt::format( "{} bravaisc: {:22.12f} {:22.12f} {:22.12f}\n", prefix, segment->bravaisc[0], segment->bravaisc[1], segment->bravaisc[2] ); + output_to_file += fmt::format( "{} ncellpoints: {}\n", prefix, segment->ncellpoints ); + output_to_file += fmt::format( "{} basis:\n", prefix); + for(int i=0; incellpoints; i++) + { + output_to_file += fmt::format( "{} {:22.12f} {:22.12f} {:22.12f}\n", prefix, segment->basis[3*i], segment->basis[3*i+1], segment->basis[3*i+2] ); + } + + n_rows = segment->n_cells[0] * segment->n_cells[1] * segment->n_cells[2] * segment->ncellpoints; } else { @@ -359,7 +420,7 @@ namespace write file_handle handle(file->file_name, false); file->n_segments = 0; file->version = 2; - handle.write( {top_header_string(), output_to_file} ); + handle.write( {top_header_string(file->ovf_extension_format), output_to_file} ); } file->found = true; file->is_ovf = true; diff --git a/include/ovf.h b/include/ovf.h index 743651d..c86b4fe 100644 --- a/include/ovf.h +++ b/include/ovf.h @@ -24,6 +24,11 @@ #define OVF_ERROR -2 #define OVF_INVALID -3 +/* OVF file formats */ +#define OVF_EXTENSION_FORMAT_OVF 0 // Standard OVF 2 format +#define OVF_EXTENSION_FORMAT_AOVF 1 // Atomistic extension, with new keywords +#define OVF_EXTENSION_FORMAT_AOVF_COMP 2 // Atomistic extension in compatibility format, (##% prefix for new keywords) + /* OVF data formats */ #define OVF_FORMAT_BIN 0 #define OVF_FORMAT_BIN4 1 @@ -55,6 +60,13 @@ struct ovf_segment { float lattice_constant; float origin[3]; + /* fields for the atomistic extension */ + float bravaisa[3]; + float bravaisb[3]; + float bravaisc[3]; + int ncellpoints; + float * basis; + /* then some "private" internal fields */ }; @@ -64,6 +76,7 @@ struct parser_state; /* the main struct which keeps the info on the main header of a file */ struct ovf_file { const char * file_name; + char * version_string; int version; @@ -74,6 +87,9 @@ struct ovf_file { /* number of segments the file should contain */ int n_segments; + /* The extension format to be used (prefixes new keywords with ##%) */ + int ovf_extension_format; + /* then some "private" internal fields */ struct parser_state * _state; }; diff --git a/python/ovf/ovf.py b/python/ovf/ovf.py index fdaf6ff..276b1dd 100644 --- a/python/ovf/ovf.py +++ b/python/ovf/ovf.py @@ -17,6 +17,10 @@ FILEFORMAT_TEXT = 3 FILEFORMAT_CSV = 4 +EXTENSION_FORMAT_OVF = 0 +EXTENSION_FORMAT_AOVF = 1 +EXTENSION_FORMAT_AOVF_COMP = 2 + class ovf_segment(ctypes.Structure): ### Some properties _fields_ = [ @@ -34,12 +38,38 @@ class ovf_segment(ctypes.Structure): ("bounds_min", ctypes.c_float*3), ("bounds_max", ctypes.c_float*3), ("lattice_constant", ctypes.c_float), - ("origin", ctypes.c_float*3) + ("origin", ctypes.c_float*3), + ("_bravaisa", ctypes.c_float*3), + ("_bravaisb", ctypes.c_float*3), + ("_bravaisc", ctypes.c_float*3), + ("ncellpoints", ctypes.c_int), + ("_basis", ctypes.POINTER(ctypes.c_float)) ] + @property + def bravaisa(self): + print("Getting value...") + return [i for i in self._bravaisa] + + @property + def bravaisb(self): + print("Getting value...") + return [i for i in self._bravaisb] + + @property + def bravaisc(self): + print("Getting value...") + return [i for i in self._bravaisc] + + @property + def basis(self): + print("Getting value...") + return [ [self._basis[3*i], self._basis[3*i+1], self._basis[3*i+2]] for i in range(self.ncellpoints)] + def __init__(self, title="", comment="", valuedim=1, valueunits="", valuelabels="", meshtype="", meshunits="", step_size=[0.0, 0.0, 0.0], bounds_min=[0.0, 0.0, 0.0], bounds_max=[0.0, 0.0, 0.0], lattice_constant=0.0, - origin=[0.0, 0.0, 0.0], pointcount=0, n_cells=[1,1,1]): + origin=[0.0, 0.0, 0.0], pointcount=0, n_cells=[1,1,1], + bravaisa = [0.0, 0.0, 0.0], bravaisb = [0.0, 0.0, 0.0], bravaisc = [0.0, 0.0, 0.0], basis = [[0,0,0]]): self.title = title.encode('utf-8') self.comment = comment.encode('utf-8') @@ -49,8 +79,21 @@ def __init__(self, title="", comment="", valuedim=1, valueunits="", valuelabels= self.meshtype = meshtype.encode('utf-8') self.meshunits = meshunits.encode('utf-8') self.pointcount = pointcount + + self.ncellpoints = len(basis) + self._basis = ctypes.cast( (ctypes.c_float * self.ncellpoints * 3)(), ctypes.POINTER(ctypes.c_float) ) + + for i in range(self.ncellpoints): + self._basis[3*i] = basis[i][0] + self._basis[3*i + 1] = basis[i][1] + self._basis[3*i + 2] = basis[i][2] + for i in range(3): self.n_cells[i] = n_cells[i] + self._bravaisa[i] = bravaisa[i] + self._bravaisb[i] = bravaisb[i] + self._bravaisc[i] = bravaisc[i] + self.N = n_cells[0]*n_cells[1]*n_cells[2] for i in range(3): self.step_size[i] = step_size[i] @@ -108,10 +151,12 @@ class _ovf_file(ctypes.Structure): ### Some properties _fields_ = [ ("file_name", ctypes.c_char_p), + ("version_string", ctypes.c_char_p), ("version", ctypes.c_int), ("found", ctypes.c_bool), ("is_ovf", ctypes.c_bool), ("n_segments", ctypes.c_int), + ("ovf_extension_format", ctypes.c_int), ("_state", ctypes.c_void_p) ] diff --git a/python/test/simple.py b/python/test/simple.py index 4a49812..f3f64ab 100644 --- a/python/test/simple.py +++ b/python/test/simple.py @@ -90,6 +90,99 @@ def test_write(self): self.assertTrue( success == ovf.OK ) print("----- ovf test reading done") + def test_atomistic(self): + print("----- ovf test writing atomistic") + with ovf.ovf_file("testfile_atomistic_py.aovf") as ovf_file: + ovf_file.ovf_extension_format = ovf.EXTENSION_FORMAT_AOVF_COMP + data = np.zeros((2, 2, 1, 3), dtype='d') + data[0,1,0,:] = [3.0, 2.0, 1.0] + + segment = ovf.ovf_segment( + title="python write test", + comment="more details in this comment...", + meshtype="lattice", + valuedim=3, + n_cells=[1,2,1], + basis = [[0,0,0], [0.2, 0.2, 0.2]], + bravaisa=[1,0,0], + bravaisb=[0,1,0], + bravaisc=[0,0,1] + ) + + success = ovf_file.write_segment(segment, data) + if success != ovf.OK: + print("write_segment failed: ", ovf_file.get_latest_message()) + self.assertTrue( success == ovf.OK ) + data[0,1,0,:] = [4.0, 5.0, 6.0] + segment.title = "python append test".encode('utf-8') + success = ovf_file.append_segment(segment, data) + if success != ovf.OK: + print("append_segment failed: ", ovf_file.get_latest_message()) + self.assertTrue( success == ovf.OK ) + print("----- ovf test writing atomistic done") + + print("----- ovf test reading atomistic") + with ovf.ovf_file("testfile_atomistic_py.aovf") as ovf_file: + pass + print("found: ", ovf_file.found) + print("is_ovf: ", ovf_file.is_ovf) + print("version: ", ovf_file.version) + print("n_segments: ", ovf_file.n_segments) + print("extension_format: ", ovf_file.ovf_extension_format) + + self.assertTrue( ovf_file.found == True ) + self.assertTrue( ovf_file.is_ovf == True ) + self.assertTrue( ovf_file.ovf_extension_format == ovf.EXTENSION_FORMAT_AOVF_COMP ) + + self.assertTrue( ovf_file.version == 1 ) + self.assertTrue( ovf_file.n_segments == 2 ) + segment = ovf.ovf_segment() + success = ovf_file.read_segment_header(0, segment) + if success != ovf.OK: + print("read_segment_header failed: ", ovf_file.get_latest_message()) + self.assertTrue( success == ovf.OK ) + + print("ncellpoints", segment.ncellpoints) + print("N", segment.N) + + self.assertEqual(segment.ncellpoints, 2) + self.assertEqual(segment.N, 4) + + print("bravaisa: ", segment.bravaisa) + print("bravaisb: ", segment.bravaisb) + print("bravaisac: ", segment.bravaisc) + + self.assertAlmostEqual( segment.bravaisa, [1,0,0] ) + self.assertAlmostEqual( segment.bravaisb, [0,1,0] ) + self.assertAlmostEqual( segment.bravaisc, [0,0,1] ) + + print("basis: ", segment.basis) + basis_expected = [[0,0,0], [0.2, 0.2, 0.2]] + [self.assertAlmostEqual( b, be ) for b, be in zip(segment.basis[0], basis_expected[0]) ] + [self.assertAlmostEqual( b, be ) for b, be in zip(segment.basis[1], basis_expected[1]) ] + + data_shape = (segment.n_cells[0], segment.n_cells[1], segment.n_cells[2], segment.ncellpoints, 3) + data = np.zeros(data_shape, dtype='f') + print("data shape: ", data_shape) + success = ovf_file.read_segment_data(0, segment, data) + if success != ovf.OK: + print("read_segment_data failed: ", ovf_file.get_latest_message()) + print("first segment: ", data[0,1,0,:]) + self.assertTrue( success == ovf.OK ) + + success = ovf_file.read_segment_header(1, segment) + if success != ovf.OK: + print("read_segment_header failed: ", ovf_file.get_latest_message()) + self.assertTrue( success == ovf.OK ) + data_shape = (segment.n_cells[0], segment.n_cells[1], segment.n_cells[2], segment.ncellpoints, 3) + data = np.zeros(data_shape, dtype='d') + success = ovf_file.read_segment_data(1, segment, data) + if success != ovf.OK: + print("read_segment_data failed: ", ovf_file.get_latest_message()) + print("second segment: ", data[0,1,0,:]) + self.assertTrue( success == ovf.OK ) + print("----- ovf test reading atomistic done") + ######### diff --git a/src/ovf.cpp b/src/ovf.cpp index 5303243..cf6c025 100644 --- a/src/ovf.cpp +++ b/src/ovf.cpp @@ -15,6 +15,7 @@ try ovf_file_ptr->is_ovf = false; ovf_file_ptr->n_segments = 0; ovf_file_ptr->_state = new parser_state; + ovf_file_ptr->ovf_extension_format = OVF_EXTENSION_FORMAT_OVF; // Check if the file exists std::fstream filestream( filename ); @@ -47,13 +48,13 @@ catch( ... ) void ovf_segment_initialize(struct ovf_segment * ovf_segment_ptr) try { - ovf_segment_ptr->title = const_cast(""); - ovf_segment_ptr->comment = const_cast(""); + ovf_segment_ptr->title = strdup(""); + ovf_segment_ptr->comment = strdup(""); ovf_segment_ptr->valuedim = 0; - ovf_segment_ptr->valueunits = const_cast(""); - ovf_segment_ptr->valuelabels = const_cast(""); - ovf_segment_ptr->meshtype = const_cast(""); - ovf_segment_ptr->meshunit = const_cast(""); + ovf_segment_ptr->valueunits = strdup(""); + ovf_segment_ptr->valuelabels = strdup(""); + ovf_segment_ptr->meshtype = strdup(""); + ovf_segment_ptr->meshunit = strdup(""); ovf_segment_ptr->pointcount = 0; ovf_segment_ptr->n_cells[0] = 0; ovf_segment_ptr->n_cells[1] = 0; diff --git a/test/atomistic.cpp b/test/atomistic.cpp new file mode 100644 index 0000000..7daffb7 --- /dev/null +++ b/test/atomistic.cpp @@ -0,0 +1,268 @@ +#include + +#include +#include + +#include +#include + +void ovf_test_write(std::string filename, std::string meshtype, int ovf_extension_format) +{ + INFO( fmt::format("Testing 'ovf_test_write' with filename '{}', meshtype '{}', ovf_extension_format '{}'", filename, meshtype, ovf_extension_format) ); + + auto create_test_segment = [&]() + { + // segment header + auto segment = ovf_segment_create(); + segment->title = strdup("ovf test title - write"); + segment->comment = strdup("test write"); + segment->valuedim = 3; + + segment->n_cells[0] = 2; + segment->n_cells[1] = 1; + segment->n_cells[2] = 1; + + if(meshtype == "rectangular") + segment->n_cells[1] = 2; + + // Fill in atomistic values + segment->bravaisa[0] = 1; + segment->bravaisa[1] = 0; + segment->bravaisa[2] = 0; + + segment->bravaisb[0] = 0; + segment->bravaisb[1] = 1; + segment->bravaisb[2] = 0; + + segment->bravaisc[0] = 0; + segment->bravaisc[1] = 0; + segment->bravaisc[2] = 1; + + segment->pointcount = 4; + + segment->ncellpoints = 2; + float basis[6] = { + 0,0.1,0.2, + 2,3,4 + }; + + segment->basis = (float *) malloc( segment->ncellpoints * 3 * sizeof(float) ); + for( int ib=0; ib < segment->ncellpoints; ib++) + { + for(int i=0; i<3; i++) + { + segment->basis[ib*3 + i] = basis[ib*3 + i]; + } + } + + segment->N = 4; + segment->meshtype = strdup(meshtype.c_str()); + return segment; + }; + + auto create_dummy_segment = [&]() + { + // Append second + auto segment = ovf_segment_create(); + segment->title = strdup("ovf test title - append"); + segment->comment = strdup("test append"); + segment->valuedim = 3; + segment->n_cells[0] = 2; + segment->n_cells[1] = 2; + segment->n_cells[2] = 1; + segment->N = 4; + return segment; + }; + + // Create and write test_segment + auto segment = create_test_segment(); + // data + std::vector field(3*segment->N, 1); + field[0] = 3; + field[3] = 2; + field[6] = 1; + field[9] = 0; + + // open + auto file = ovf_open(filename.c_str()); + file->ovf_extension_format = ovf_extension_format; // Set the flag for the atomistic extension + + // write + int success = ovf_write_segment_8(file, segment, field.data(), OVF_FORMAT_TEXT); + if( OVF_OK != success ) + std::cerr << ovf_latest_message(file) << std::endl; + + if( ovf_extension_format == OVF_EXTENSION_FORMAT_OVF && meshtype=="lattice" ) // do not allow lattice for non AOVF extension + { + REQUIRE( success == OVF_ERROR ); + } else { + REQUIRE( success == OVF_OK ); + } + // close + ovf_close(file); + + + // Create and append dummy_segment + // data + segment = create_dummy_segment(); + std::vector field_dummy(3*segment->N, 1); + field_dummy[0] = 6; + field_dummy[3] = 4; + field_dummy[6] = 2; + field_dummy[9] = 0; + + // open + file = ovf_open(filename.c_str()); + // write + success = ovf_append_segment_8(file, segment, field.data(), OVF_FORMAT_CSV); + if( OVF_OK != success ) + std::cerr << ovf_latest_message(file) << std::endl; + REQUIRE( success == OVF_OK ); + // close + ovf_close(file); + + + // Create and append test_segment + // data + segment = create_test_segment(); + // open + file = ovf_open(filename.c_str()); + // write + success = ovf_append_segment_8(file, segment, field.data(), OVF_FORMAT_CSV); + if( OVF_OK != success ) + std::cerr << ovf_latest_message(file) << std::endl; + + if( ovf_extension_format == OVF_EXTENSION_FORMAT_OVF && meshtype=="lattice" ) // do not allow lattice for non AOVF extension + { + REQUIRE( success == OVF_ERROR ); + } else { + REQUIRE( success == OVF_OK ); + } + + // close + ovf_close(file); + +} + + +void ovf_test_read(std::string filename, std::string meshtype, int ovf_extension_format) +{ + INFO( fmt::format("Testing 'ovf_test_read' with filename '{}', meshtype '{}', ovf_extension_format '{}'", filename, meshtype, ovf_extension_format) ); + + auto verify_test_segment = [&](int index) + { + // open + auto file = ovf_open(filename.c_str()); + std::cerr << ovf_latest_message(file) << std::endl; + REQUIRE( file->found == true ); + REQUIRE( file->is_ovf == true ); + REQUIRE( file->n_segments == 3); + REQUIRE( file->ovf_extension_format == ovf_extension_format ); + + // segment header + auto segment = ovf_segment_create(); + + // read header + int success = ovf_read_segment_header(file, index, segment); + if( OVF_OK != success ) + std::cerr << ovf_latest_message(file) << std::endl; + REQUIRE( success == OVF_OK ); + + REQUIRE( segment->N == 4 ); + REQUIRE( std::string(segment->meshtype) == meshtype ); + + if(std::string(segment->meshtype) == "lattice") + { + REQUIRE( segment->ncellpoints == 2); + REQUIRE( segment->basis[0] == 0); + + REQUIRE( segment->basis[1] == 0.1f); + REQUIRE( segment->basis[2] == 0.2f); + REQUIRE( segment->basis[3] == 2); + REQUIRE( segment->basis[4] == 3); + REQUIRE( segment->basis[5] == 4); + + REQUIRE( segment->bravaisa[0] == 1); + REQUIRE( segment->bravaisa[1] == 0); + REQUIRE( segment->bravaisa[2] == 0); + + REQUIRE( segment->bravaisb[0] == 0); + REQUIRE( segment->bravaisb[1] == 1); + REQUIRE( segment->bravaisb[2] == 0); + + REQUIRE( segment->bravaisc[0] == 0); + REQUIRE( segment->bravaisc[1] == 0); + REQUIRE( segment->bravaisc[2] == 1); + } + // data + std::vector field(3*segment->N); + + // read data + success = ovf_read_segment_data_4(file, index, segment, field.data()); + if( OVF_OK != success ) + std::cerr << ovf_latest_message(file) << std::endl; + REQUIRE( success == OVF_OK ); + + REQUIRE( field[0] == 3 ); + REQUIRE( field[1] == 1 ); + REQUIRE( field[3] == 2 ); + REQUIRE( field[6] == 1 ); + REQUIRE( field[9] == 0 ); + + // close + ovf_close(file); + }; + + verify_test_segment(0); + verify_test_segment(2); +} + +TEST_CASE( "Atomistic Write", "[write]" ) +{ + const char * testfile = "testfile_cpp.aovf"; + + SECTION( "write OVF" ) + { + ovf_test_write( "test_file_ovf_rectangular_cpp.ovf", "rectangular", OVF_EXTENSION_FORMAT_OVF ); + ovf_test_write( "test_file_ovf_irregular_cpp.ovf", "irregular", OVF_EXTENSION_FORMAT_OVF ); + ovf_test_write( "test_file_ovf_lattice_cpp.ovf", "lattice", OVF_EXTENSION_FORMAT_OVF ); // Should print some error message + } + + SECTION( "write AOVF" ) + { + ovf_test_write( "test_file_atomistic_rectangular_cpp.aovf", "rectangular", OVF_EXTENSION_FORMAT_AOVF ); + ovf_test_write( "test_file_atomistic_lattice_cpp.aovf", "lattice", OVF_EXTENSION_FORMAT_AOVF ); + ovf_test_write( "test_file_atomistic_irregular_cpp.aovf", "irregular", OVF_EXTENSION_FORMAT_AOVF ); + } + + SECTION( "write AOVF_COMP" ) + { + ovf_test_write( "test_file_atomistic_comp_rectangular_cpp.aovf", "rectangular", OVF_EXTENSION_FORMAT_AOVF_COMP ); + ovf_test_write( "test_file_atomistic_comp_lattice_cpp.aovf", "lattice", OVF_EXTENSION_FORMAT_AOVF_COMP ); + ovf_test_write( "test_file_atomistic_comp_irregular_cpp.aovf", "irregular", OVF_EXTENSION_FORMAT_AOVF_COMP ); + } +} + +TEST_CASE( "Atomistic Read", "[read]" ) +{ + SECTION( "read OVF" ) + { + ovf_test_write( "test_file_ovf_rectangular_cpp.ovf", "rectangular", OVF_EXTENSION_FORMAT_OVF ); + ovf_test_write( "test_file_ovf_irregular_cpp.ovf", "irregular", OVF_EXTENSION_FORMAT_OVF ); + // ovf_test_write( "test_file_ovf_lattice_cpp.ovf", "lattice", OVF_EXTENSION_FORMAT_OVF ); // should not be written at all + } + + SECTION( "read AOVF" ) + { + ovf_test_read( "test_file_atomistic_rectangular_cpp.aovf", "rectangular", OVF_EXTENSION_FORMAT_AOVF ); + ovf_test_read( "test_file_atomistic_lattice_cpp.aovf", "lattice", OVF_EXTENSION_FORMAT_AOVF ); + ovf_test_read( "test_file_atomistic_irregular_cpp.aovf", "irregular", OVF_EXTENSION_FORMAT_AOVF ); + } + + SECTION( "read AOVF_COMP" ) + { + ovf_test_read( "test_file_atomistic_comp_rectangular_cpp.aovf", "rectangular", OVF_EXTENSION_FORMAT_AOVF_COMP ); + ovf_test_read( "test_file_atomistic_comp_lattice_cpp.aovf", "lattice", OVF_EXTENSION_FORMAT_AOVF_COMP ); + ovf_test_read( "test_file_atomistic_comp_irregular_cpp.aovf", "irregular", OVF_EXTENSION_FORMAT_AOVF_COMP ); + } +} \ No newline at end of file diff --git a/test/binary.cpp b/test/binary.cpp index a1e8337..beb30d3 100644 --- a/test/binary.cpp +++ b/test/binary.cpp @@ -1,6 +1,7 @@ #include #include +#include #include @@ -152,8 +153,8 @@ TEST_CASE( "Mixed binary and CSV", "[mixed]" ) { // segment header auto segment = ovf_segment_create(); - segment->title = const_cast("ovf test title - write"); - segment->comment = const_cast("test write csv"); + segment->title = strdup("ovf test title - write"); + segment->comment = strdup("test write csv"); segment->valuedim = 3; segment->n_cells[0] = 2; segment->n_cells[1] = 2; diff --git a/test/simple.cpp b/test/simple.cpp index 54cec8e..cea5643 100644 --- a/test/simple.cpp +++ b/test/simple.cpp @@ -1,6 +1,7 @@ #include #include +#include #include @@ -24,8 +25,8 @@ TEST_CASE( "Write", "[write]" ) { // segment header auto segment = ovf_segment_create(); - segment->title = const_cast("ovf test title - write"); - segment->comment = const_cast("test write"); + segment->title = strdup("ovf test title - write"); + segment->comment = strdup("test write"); segment->valuedim = 3; segment->n_cells[0] = 2; segment->n_cells[1] = 2; @@ -56,8 +57,8 @@ TEST_CASE( "Write", "[write]" ) { // segment header auto segment = ovf_segment_create(); - segment->title = const_cast("ovf test title - append"); - segment->comment = const_cast("test append"); + segment->title = strdup("ovf test title - append"); + segment->comment = strdup("test append"); segment->valuedim = 3; segment->n_cells[0] = 2; segment->n_cells[1] = 2; @@ -88,10 +89,10 @@ TEST_CASE( "Write", "[write]" ) { // segment header auto segment = ovf_segment_create(); - segment->title = const_cast("ovf test title - append irregular mesh"); - segment->comment = const_cast("an irregular mesh has different keywords than a rectangular one"); + segment->title = strdup("ovf test title - append irregular mesh"); + segment->comment = strdup("an irregular mesh has different keywords than a rectangular one"); segment->valuedim = 3; - segment->meshtype = const_cast("irregular"); + segment->meshtype = strdup("irregular"); segment->pointcount = 4; // data diff --git a/test/weird_formatting.cpp b/test/weird_formatting.cpp new file mode 100644 index 0000000..941f391 --- /dev/null +++ b/test/weird_formatting.cpp @@ -0,0 +1,375 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +enum class VERSION +{ + OVF, + AOVF, + AOVF_COMP, + RANDOM +}; + +enum class MESHTYPE +{ + IRREGULAR, + RECTANGULAR, + LATTICE, + RANDOM +}; + +// Helper struct to create a "weirdly" formatted OVF file +struct test_ovf_file +{ + int random_seed; + bool shuffle = true; + int n_whitespace = 4; + int n_skippable_lines = 4; + + std::mt19937 g; + + // Some information we will need for the tests + bool should_be_valid; + VERSION version; + MESHTYPE meshtype; + + std::string file_string = ""; + bool use_magic_char_comment = true; + + using kw_val_pair = std::pair; + + std::vector< kw_val_pair > pairs; + std::vector< kw_val_pair > pairs_lattice; + std::vector< kw_val_pair > pairs_irregular; + std::vector< kw_val_pair > pairs_rectangular; + + test_ovf_file(VERSION version, MESHTYPE meshtype, int random_seed) : version(version), meshtype(meshtype), random_seed(random_seed) + { + srand(random_seed); + + pairs_irregular = + { + {"meshtype", "irregular"}, + {"pointcount", "4"} + }; + + pairs_lattice = + { + {"meshtype", "lattice"}, + {"anodes", "2"}, + {"bnodes", "1"}, + {"cnodes", "1"}, + {"bravaisa", "1 0 0"}, + {"bravaisb", "0 1 0"}, + {"bravaisc", "0 0 1"}, + {"basis", "\n# 0 0 0\n# 0.1 0.1 0.1"}, + {"ncellpoints", "2"} + }; + + pairs_rectangular = + { + {"meshtype", "rectangular"}, + {"xbase", "2"}, + {"ybase", "1"}, + {"zbase", "1"}, + {"xstepsize", "0"}, + {"ystepsize", "0"}, + {"zstepsize", "0"}, + {"xnodes", "4"}, + {"ynodes", "1"}, + {"znodes", "1"} + }; + + pairs = + { + {"xmin", "0"}, + {"ymin", "0"}, + {"zmin", "0"}, + {"xmax", "0"}, + {"ymax", "0"}, + {"zmax", "0"}, + {"Title", "my a title"}, + {"Desc", "the description"}, + {"valuedim", " 3"}, + {"valueunits", " eV "}, + {"valuelabels", " "}, + {"meshunit", " nm "} + }; + } + + std::string random_whitespace() + { + if( n_whitespace < 1) + return ""; + + std::string result; + for (int i=0; i<(rand()%n_whitespace); i++) + result += " "; + + return std::move(result); + } + + std::string comment() + { + std::string result; + + result += "##"; + if(use_magic_char_comment) + { + if(rand()%2 == 0) + { + result += "%"; + } + } + result += " " + random_whitespace() + "comment" + random_whitespace(); + + return std::move(result); + } + + std::string line_end() + { + std::string result; + result += random_whitespace() + "\n"; + return std::move(result); + } + + void append_skippable_lines( ) + { + if( n_skippable_lines < 1) + return; + for (int i=0; i<(rand()%n_skippable_lines); i++) + { + if(rand()%2 == 0) + file_string += comment() + line_end(); + if(rand()%2 == 0) + file_string += "#" + line_end(); + } + } + + std::string separate_with_whitespace(const std::vector & in) + { + std::string result; + for(auto & s : in) + { + result += random_whitespace( ) + s; + } + return std::move(result); + } + + void append_version() + { + if( version == VERSION::OVF) + { + file_string += "#" + separate_with_whitespace({"OOMMF OVF ", "2.0"}) + line_end(); + } else if( version == VERSION::AOVF ) + { + file_string += "#" + separate_with_whitespace({"AOVF ", "1.0"}) + line_end(); + } else if( version == VERSION::AOVF_COMP ) + { + file_string += "#" + separate_with_whitespace({"OOMMF OVF ", "2.0"}) + line_end(); + file_string += "##%" + separate_with_whitespace({"AOVF ", "1.0"}) + line_end(); + use_magic_char_comment = false; + } + } + + void append_segment_count() + { + file_string += "# " + separate_with_whitespace({"Segment count: ", "000001"}) + line_end(); + } + + void append_begin_segment() + { + file_string += "#" + separate_with_whitespace({"Begin: ", "Segment"}) + line_end(); + } + + void append_end_segment() + { + file_string += "#" + separate_with_whitespace({"End: ", "Segment"}) + line_end(); + } + + void append_begin_header() + { + file_string += "#" + separate_with_whitespace({"Begin: ", "Header"}) + line_end(); + } + + void append_end_header() + { + file_string += "#" + separate_with_whitespace({"End: ", "Header"}) + line_end(); + } + + void append_pairs() + { + std::vector file_pairs(0); // pairs with # prefix + std::vector magic_file_pairs(0); // pairs with ##% prefix + + // Insert all the pairs that always have to be present + file_pairs.insert(file_pairs.end(), pairs.begin(), pairs.end()); + + // if(meshtype == MESHTYPE::RANDOM); + // meshtype = static_cast( rand() % (int(MESHTYPE::RANDOM)) ); + + if(meshtype == MESHTYPE::LATTICE) + { + if(version == VERSION::AOVF_COMP) + { + file_pairs.push_back({"meshtype", "rectangular"}); + file_pairs.insert(file_pairs.end(), pairs_rectangular.begin(), pairs_rectangular.end()); + } + file_pairs.insert(file_pairs.end(), pairs_lattice.begin(), pairs_lattice.end()); + } else if(meshtype == MESHTYPE::RECTANGULAR) + { + file_pairs.insert(file_pairs.end(), pairs_rectangular.begin(), pairs_rectangular.end()); + } else if(meshtype == MESHTYPE::IRREGULAR) + { + file_pairs.insert(file_pairs.end(), pairs_irregular.begin(), pairs_irregular.end()); + } + + // std::random_device rd; + g = std::mt19937(random_seed); + + if(shuffle) + std::shuffle(file_pairs.begin(), file_pairs.end(), g); + + for(auto & p : file_pairs) + { + std::string prefix = "#"; + + if( meshtype==MESHTYPE::LATTICE && version==VERSION::AOVF_COMP) + { + if( std::find( pairs_lattice.begin(), pairs_lattice.end(), p ) != pairs_lattice.end() ) + { + prefix += "#%"; + } + } + + if(p.first == std::string("basis")) + { + file_string += prefix + "basis: " + line_end() + prefix + "0 0 0" + line_end() + prefix + "0 0 0" + + line_end(); + } else { + file_string += prefix + separate_with_whitespace({p.first + ":", p.second}) + line_end(); + } + append_skippable_lines(); + } + } + + void append_data() + { + file_string += + "# Begin: Data Text\n" + "0.000000000000 0.000000000000 0.000000000000\n" + "3.000000000000 2.000000000000 1.000000000000\n" + "0.000000000000 0.000000000000 0.000000000000\n" + "0.000000000000 0.000000000000 0.000000000000\n" + "# End: Data Text\n"; + } + + void write(std::string filename) + { + file_string.clear(); + append_version(); + // append_skippable_lines(); + append_segment_count(); + append_begin_segment(); + append_skippable_lines(); + append_begin_header(); + append_skippable_lines(); + append_pairs(); + append_skippable_lines(); + append_end_header(); + append_skippable_lines(); + append_data(); + append_skippable_lines(); + append_end_segment(); + + std::ofstream out; + out.open(filename); + out << file_string; + out.close(); + } + +}; + + +// open +void test_ovf_read(std::string filename, const test_ovf_file & test_file) +{ + INFO(fmt::format("Testing 'ovf_test_read' with filename '{}', meshtype '{}', ovf_extension_format '{}'", filename, int(test_file.meshtype), int(test_file.version))); + + auto file = ovf_open(filename.c_str()); + std::cerr << ovf_latest_message(file) << std::endl; + REQUIRE( file->found == true ); + REQUIRE( file->is_ovf == true ); + REQUIRE( file->n_segments == 1); + + // segment header + auto segment = ovf_segment_create(); + + // read header + int success = ovf_read_segment_header(file, 0, segment); + if( OVF_OK != success ) + std::cerr << ovf_latest_message(file) << std::endl; + REQUIRE( success == OVF_OK ); +} + +TEST_CASE("READ") +{ + test_ovf_file file = test_ovf_file(VERSION::OVF, MESHTYPE::RECTANGULAR, 1337); + + file.n_skippable_lines = 4; + file.n_whitespace = 4; + file.shuffle = true; + + for(int i=0; i<3; i++) + { + // OVF 2.0 + file.meshtype = MESHTYPE::RECTANGULAR; + file.write("test_weird_ovf_rectangular.ovf"); + test_ovf_read("test_weird_ovf_rectangular.ovf", file); + + file.meshtype = MESHTYPE::IRREGULAR; + file.write("test_weird_ovf_irregular.ovf"); + test_ovf_read("test_weird_ovf_irregular.ovf", file); + + // AOVF + file.version = VERSION::AOVF; + file.meshtype = MESHTYPE::RECTANGULAR; + file.write("test_weird_aovf_rectangular.ovf"); + test_ovf_read("test_weird_aovf_rectangular.ovf", file); + + file.meshtype = MESHTYPE::IRREGULAR; + file.write("test_weird_aovf_irregular.ovf"); + test_ovf_read("test_weird_aovf_irregular.ovf", file); + + file.meshtype = MESHTYPE::LATTICE; + file.write("test_weird_aovf_lattice.ovf"); + test_ovf_read("test_weird_aovf_lattice.ovf", file); + + // AOVF_COMP + file.version = VERSION::AOVF_COMP; + file.meshtype = MESHTYPE::RECTANGULAR; + file.write("test_weird_caovf_rectangular.ovf"); + test_ovf_read("test_weird_caovf_rectangular.ovf", file); + + file.meshtype = MESHTYPE::IRREGULAR; + file.write("test_weird_caovf_irregular.ovf"); + test_ovf_read("test_weird_caovf_irregular.ovf", file); + + file.meshtype = MESHTYPE::LATTICE; + file.write("test_weird_caovf_lattice.ovf"); + test_ovf_read("test_weird_caovf_lattice.ovf", file); + } +} \ No newline at end of file