diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c795b054e5ade51b7031abab1581a5b7e2d2f5ba --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..a841b772c253dd35c10ed07f0d3946c3cc6ce7ec --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,20 @@ +image: mwm126/mfix-exa:cuda + +variables: + GIT_STRATEGY: fetch + GIT_SUBMODULE_STRATEGY: normal + +###### Test jobs ##### + +test:all: + script: + - cmake -S . + -B build + -G Ninja + - cmake --build build --target unit_tests + - ./build/src/tests/unit_tests + - cmake --build build --target mfix-parser + - find -name inputs + - find -name inputs |xargs -l ./build/mfix-parser + tags: + - mfix-exa diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..25ed73af54148974fefaaafa44cbd70eece54ed1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "PEGTL"] + path = subprojects/PEGTL + url = https://github.com/taocpp/PEGTL +[submodule "mfix/subprojects/mfix"] + path = subprojects/mfix + url = ../mfix +[submodule "subprojects/Catch2"] + path = subprojects/Catch2 + url = https://github.com/catchorg/Catch2 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..b70011512f33750da8577f457358c4a21b54beaf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.14) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +project(MFIX-Parser + DESCRIPTION "A parser for MFIX-Exa input files" + HOMEPAGE_URL "https://mfix.netl.doe.gov/gitlab/exa/mfix-parser" + LANGUAGES CXX + ) + +set( CMAKE_EXPORT_COMPILE_COMMANDS ON ) +set(USE_CCACHE "") +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set( CMAKE_CXX_COMPILER_LAUNCHER ccache ) +endif() + +add_subdirectory(src) + +set(EXENAME "mfix-parser") +add_executable(${EXENAME} src/main.cpp) +target_link_libraries(${EXENAME} parser) diff --git a/README.md b/README.md index 3dad396d5249b4dd2e9a86556cc9150787ac3db5..fccc9c2a89ea5fa5b9d873e6473e28fe3064c1fb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,26 @@ # mfix-parser -Repo for parsing and validating MFIX input(s) files for use by mfix-app \ No newline at end of file +Repo for parsing and validating MFIX input(s) files for use by mfix-app. + +Uses PEGTL for parsing and Catch2 for tests. + +This has the MFIX-Exa repo as a submodule in order to test against existing ``inputs`` files. + + +## Build + +> python3 pip install cmake # if CMake not installed +> cmake -S. -Bbuild +> cmake --build build +> cd build +> ctest + +## Run Tests + +> cmake --build build --target unit_tests +> ./build/src/tests/unit_tests + +## Run on inputs file + +> cmake --build build --target mfix-parser +> ./build/mfix-parser subprojects/mfix/benchmarks/01-HCS/Size0001/inputs && echo "Parsing succeeded." diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..d5adbfc1e6896cea26526fb31576048d7210518e --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,28 @@ +################################################################################ +# inputs Parser +################################################################################ + +add_library(parser + parser.cpp + ) + +target_include_directories(parser PRIVATE + ${CMAKE_SOURCE_DIR}/subprojects/PEGTL/include) + +target_link_libraries(parser PRIVATE stdc++fs) + +add_subdirectory(tests) + +find_program(CLANG_FMT NAMES clang-format-9 clang-format-8 clang-format-7 clang-format) + +if(CLANG_FMT) + get_target_property(_psrcs unit_tests SOURCES) + add_custom_target(fmt_test + COMMAND ${CLANG_FMT} -i ${_psrcs} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests + ) + file(GLOB _includes ${PROJECT_SOURCE_DIR}/src/*.hpp) + + add_custom_target(fmt) + add_dependencies(fmt fmt_test) +endif() diff --git a/src/double.hpp b/src/double.hpp new file mode 100644 index 0000000000000000000000000000000000000000..80e8115f1fc206bf0827afa14a6c655baff30b15 --- /dev/null +++ b/src/double.hpp @@ -0,0 +1,44 @@ +// Copyright (c) 2014-2019 Dr. Colin Hirsch and Daniel Frey +// Please see LICENSE for license or visit https://github.com/taocpp/PEGTL/ + +#ifndef TAO_PEGTL_SRC_EXAMPLES_PEGTL_DOUBLE_HPP +#define TAO_PEGTL_SRC_EXAMPLES_PEGTL_DOUBLE_HPP + +#include + +namespace double_ { +// A grammar for doubles suitable for std::stod without locale support. +// See also: http://en.cppreference.com/w/cpp/string/basic_string/stof + +using namespace TAO_PEGTL_NAMESPACE; + +// clang-format off + struct plus_minus : opt< one< '+', '-' > > {}; + struct dot : one< '.' > {}; + + struct inf : seq< istring< 'i', 'n', 'f' >, + opt< istring< 'i', 'n', 'i', 't', 'y' > > > {}; + + struct nan : seq< istring< 'n', 'a', 'n' >, + opt< one< '(' >, + plus< alnum >, + one< ')' > > > {}; + + template< typename D > + struct number : if_then_else< dot, + plus< D >, + seq< plus< D >, opt< dot, star< D > > > > {}; + + struct e : one< 'e', 'E' > {}; + struct p : one< 'p', 'P' > {}; + struct exponent : seq< plus_minus, plus< digit > > {}; + + struct decimal : seq< number< digit >, opt< e, exponent > > {}; + struct hexadecimal : seq< one< '0' >, one< 'x', 'X' >, number< xdigit >, opt< p, exponent > > {}; + + struct grammar : seq< plus_minus, sor< hexadecimal, decimal, inf, nan > > {}; +// clang-format on + +} // namespace double_ + +#endif diff --git a/src/inputs.hpp b/src/inputs.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0f91fb6acae040741ac40d42bdd1892789277e65 --- /dev/null +++ b/src/inputs.hpp @@ -0,0 +1,21 @@ +#ifndef INPUTS_H_ +#define INPUTS_H_ + +#include +#include +#include +#include +#include +#include + +namespace parser { + +using InputArray = std::vector; +using InputValue = std::variant; +using InputInfo = std::map; + +std::optional parse_inputs(std::string); + +} // namespace parser + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b4f7920eba7dba46737d9b354301ed5e36c0c366 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +#include "inputs.hpp" + +std::string read_file(std::string inputs_file) { + std::ifstream in_stream; + in_stream.open(inputs_file.c_str()); + + std::stringstream str_stream; + str_stream << in_stream.rdbuf(); + return str_stream.str(); +} + +int main(int argc, char *argv[]) { + std::string fname(argv[1]); + auto inputs_str = read_file(fname); + auto maybe_success = parser::parse_inputs(inputs_str); + if (maybe_success.has_value()) { + return 0; + } + return -1; +} diff --git a/src/parser.cpp b/src/parser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..10ac228f3dd49501b6740e2ae62c615fe3449832 --- /dev/null +++ b/src/parser.cpp @@ -0,0 +1,136 @@ +// standard includes +#include +#include + +// subproject includes +#include + +// includes +#include "double.hpp" +#include "inputs.hpp" + +using namespace TAO_PEGTL_NAMESPACE; + +namespace { + +struct parser_state { + std::string current_key; + std::vector current_vec; + parser::InputValue current_value; + parser::InputInfo info; +}; + +} // namespace + +namespace { + +struct escaped_x : seq, rep<2, must>> {}; +struct escaped_u : seq, rep<4, must>> {}; +struct escaped_U : seq, rep<8, must>> {}; +struct escaped_c + : one<'\'', '"', '?', '\\', 'a', 'b', 'f', 'n', 'r', 't', 'v'> {}; + +struct escaped : sor {}; + +struct character + : if_must_else, escaped, utf8::range<0x20, 0x10FFFF>> {}; +struct string_literal : if_must, until, character>> {}; +struct implicit_string : if_must> {}; + +struct whitespace_delimited_array : list>> {}; + +struct L_FUN : pad, space> {}; +struct R_FUN : pad, space> {}; +struct L_ARR : pad, space> {}; +struct R_ARR : pad, space> {}; +struct L_BLK : pad, space> {}; +struct R_BLK : pad, space> {}; +struct S_CLN : pad, space> {}; + +struct name : identifier {}; + +struct value + : sor {}; + +struct comment : if_must, until> {}; + +struct key : list> {}; +struct keyval : seq, string<'='>, pad> {}; +struct grammar : pad, space> {}; + +template struct action {}; + +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + std::stringstream ss(in.string()); + ss >> st.current_key; + } +}; + +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + std::stringstream ss(in.string()); + std::string v; + ss >> v; + st.current_value = v.substr(1, v.length() - 2); // strip quotes + } +}; + +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + std::stringstream ss(in.string()); + std::string v; + ss >> v; + st.current_value = v; + } +}; + +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + std::stringstream ss(in.string()); + double v; + ss >> v; + st.current_vec.push_back(v); + } +}; +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + std::stringstream ss(in.string()); + st.current_value = st.current_vec; + st.current_vec.clear(); + } +}; + +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + st.info[st.current_key] = st.current_value; + } +}; + +std::optional do_parse(std::string str) { + parser_state st; + memory_input in(str, "std::cin"); + if (!parse(in, st)) { + return std::nullopt; + } + return st; +} + +} // namespace + +namespace parser { + +std::optional parse_inputs(std::string str) { + auto maybe_state = do_parse(str); + if (!maybe_state.has_value()) { + return std::nullopt; + } + return maybe_state.value().info; +} +} // namespace parser diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..efe4fd1ea55a6aa7e5a0267d440d08c58f06d7ab --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,44 @@ +################################################################################ +# Parser Tests +################################################################################ + +add_executable(unit_tests EXCLUDE_FROM_ALL + ../parser.cpp + parser/main.cpp + parser/inputs.t.cpp + ) + +target_include_directories(unit_tests PRIVATE + ${CMAKE_SOURCE_DIR}/subprojects/PEGTL/include + ${CMAKE_SOURCE_DIR}/src + ) + +SET(catch2_dir ${PROJECT_SOURCE_DIR}/subprojects/Catch2) + +find_package(Git QUIET) +if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") + # Update submodules as needed + option(GIT_SUBMODULE "Check submodules during build" ON) + if(GIT_SUBMODULE) + message(STATUS "Submodule update") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") + endif() + endif() +endif() + +if(NOT EXISTS "${catch2_dir}/CMakeLists.txt") + message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") +endif() + +add_subdirectory(${catch2_dir} ${CMAKE_CURRENT_BINARY_DIR}/catch_build) +list(APPEND CMAKE_MODULE_PATH "${catch2_dir}/contrib/") + +target_link_libraries(unit_tests Catch2::Catch2) + +include(CTest) +include(Catch) +catch_discover_tests(unit_tests) diff --git a/src/tests/parser/inputs.t.cpp b/src/tests/parser/inputs.t.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dad519c2d12e27b282d2a69406cbb9f77ee5f6ff --- /dev/null +++ b/src/tests/parser/inputs.t.cpp @@ -0,0 +1,282 @@ +#include "catch2/catch.hpp" + +#include + +TEST_CASE("comment", "[]") { + auto maybe_st = parser::parse_inputs(R"( +# this is a comment +)"); + CHECK(maybe_st.has_value()); +} + +TEST_CASE("double_int", "[]") { + auto maybe_st = parser::parse_inputs(R"( +mfix.max_step = 100 +)"); + REQUIRE(maybe_st.has_value()); + auto info = maybe_st.value(); + auto value = info["mfix.max_step"]; + auto aa = std::get(value); + CHECK(aa.size() == 1); + CHECK(aa[0] == 100); +} + +TEST_CASE("double", "[]") { + auto maybe_st = parser::parse_inputs(R"( +mfix.stop_time = 0.20 +)"); + REQUIRE(maybe_st.has_value()); + auto info = maybe_st.value(); + auto value = info["mfix.stop_time"]; + auto aa = std::get(value); + CHECK(aa.size() == 1); + CHECK(aa[0] == 0.2); +} + +TEST_CASE("string", "[]") { + auto maybe_st = parser::parse_inputs(R"( + mfix.particle_init_type = "Auto" +)"); + REQUIRE(maybe_st.has_value()); + auto info = maybe_st.value(); + auto value = info["mfix.particle_init_type"]; + auto ss = std::get(value); + CHECK(ss == "Auto"); +} + +TEST_CASE("implicit string", "[]") { + auto maybe_st = parser::parse_inputs(R"( +fluid.solve = fluid +)"); + REQUIRE(maybe_st.has_value()); + auto info = maybe_st.value(); + auto value = info["fluid.solve"]; + auto ss = std::get(value); + CHECK(ss == "fluid"); +} + +TEST_CASE("array", "[]") { + auto maybe_st = parser::parse_inputs(R"( + mfix.gravity = -9.81 0.0 0.0 +)"); + + REQUIRE(maybe_st.has_value()); + auto info = maybe_st.value(); + auto value = info["mfix.gravity"]; + auto aa = std::get(value); + CHECK(aa.size() == 3); + CHECK(aa[0] == -9.81); + CHECK(aa[1] == 0.0); + CHECK(aa[2] == 0.0); +} + +TEST_CASE("whole_file", "[]") { + auto maybe_st = parser::parse_inputs(R"( + +#_______________________________________________________________________ +# Solver settings + +#amrex.fpe_trap_invalid = 1 + +#! Fluid solver +#!-----------------------------------------------------------// +mfix.cfl = 0.5 + +mfix.max_step = 100 +#mfix.stop_time = 0.20 + +mfix.particle_init_type = "Auto" + +mfix.gravity = -9.81 0.0 0.0 + +mfix.drag_type = "BVK2" + +mac_proj.verbose = 0 +nodal_proj.verbose = 1 + +amr.blocking_factor = 1 + +#_______________________________________________________________________ +# Geometry / grids / tiles + +# Maximum level in hierarchy (for now must be 0, i.e., one level in total) +amr.max_level = 0 + +geometry.coord_sys = 0 # 0: Cartesian +geometry.is_periodic = 0 0 0 # Is periodic in each direction? +geometry.prob_lo = 0. 0. 0. # lo corner of physical domain +geometry.prob_hi = 0.004 0.001 0.001 # hi corner of physical domain + +# Number of grid cells in each direction at the coarsest level +amr.n_cell = 20 5 5 + +#! Grids +#!-----------------------------------------------------------// +# Maximum allowable size of each fluid subdomain in the problem domain; + +# Fluid +amr.max_grid_size_x = 1024 +amr.max_grid_size_y = 1024 +amr.max_grid_size_z = 1024 + +# Particles (not with KDTree) +#particles.max_grid_size_x = 32 +#particles.max_grid_size_y = 32 +#particles.max_grid_size_z = 32 + +#! Tiles +#!-----------------------------------------------------------// + +# Fluid: Maximum tile size within each grid +fabarray.mfiter_tile_size = 1024 1024 1024 + +# Particles: Maximum particle title size +particles.tile_size = 1024 1024 1024 + + +#! EB settings +#!-----------------------------------------------------------// + +# Level-set refinement and padding +mfix.levelset__refinement = 4 # levelset resolution + +mfix.write_eb_surface = true # Needed to output eb-surfaces + + +#_______________________________________________________________________ +# Particle load balancing + +#mfix.load_balance_type = "KnapSack" + +amr.dual_grid = 0 +amr.regrid_int = -1 + +#! KnapSack settings +#!-----------------------------------------------------------// +#default is "RunTimeCosts"; options include RunTimeCosts or NumParticles +#this option is only relevant if load_balance_type = KnapSack + +#mfix.knapsack_weight_type = "NumParticles" +#mfix.knapsack_weight_type = "RunTimeCosts" + +#_______________________________________________________________________ +# IO / Checkpointing + +amr.par_ascii_int = -1 +amr.par_ascii_file ="vis" + +amr.plot_int = -1 +amr.plot_file ="plt" + +amr.check_int = -1 +amr.check_file ="chk" + +#! Restart from checkpoint +#!-----------------------------------------------------------// +#amr.restart ="chk00100" + + +#_______________________________________________________________________ +# Fluid model settings +# +fluid.solve = fluid + +fluid.density = constant +fluid.density.constant = 1.0 + +fluid.viscosity = constant +fluid.viscosity.constant = 2.0e-5 + + +#_______________________________________________________________________ +# DEM model settings +# +dem.solve = solid0 + +dem.friction_coeff.pp = 0.0 +dem.friction_coeff.pw = 0.0 + +dem.spring_const.pp = 10.0 +dem.spring_const.pw = 10.0 + +dem.restitution_coeff.solid0.solid0 = 0.8 +dem.restitution_coeff.solid0.wall = 1.0 + +dem.spring_tang_fac.pp = 0.285714285 +dem.spring_tang_fac.pw = 0.285714285 + +dem.damping_tang_fac.pp = 0.5 +dem.damping_tang_fac.pw = 0.5 + + +#_______________________________________________________________________ +# EB geometry +# +mfix.geometry = "cylinder" + +cylinder.internal_flow = true + +cylinder.radius = 0.00045 +cylinder.height = -1.0 + +cylinder.direction = 0 +cylinder.center = 0.0020 0.0005 0.0005 + + +#_______________________________________________________________________ +# Regions for defining ICs and BCs +# +mfix.regions = full-domain bed inflow outflow + +regions.full-domain.lo = 0.0000 0.0000 0.0000 +regions.full-domain.hi = 0.0040 0.0010 0.0010 + +regions.bed.lo = 0.0003 0.0000 0.0000 +regions.bed.hi = 0.0032 0.0010 0.0010 + +regions.inflow.lo = 0.0000 0.0000 0.0000 +regions.inflow.hi = 0.0000 0.0010 0.0010 + +regions.outflow.lo = 0.0040 0.0000 0.0000 +regions.outflow.hi = 0.0040 0.0010 0.0010 + +#_______________________________________________________________________ +# Initial Conditions +# +ic.regions = full-domain bed + +ic.full-domain.fluid.volfrac = 1.0 +ic.full-domain.fluid.velocity = 0.015 0.0 0.0 + +ic.bed.fluid.volfrac = 0.725 +ic.bed.fluid.velocity = 0.00 0.00 0.00 + +ic.bed.solids = solid0 +ic.bed.packing = random + +ic.bed.solid0.volfrac = 0.275 +ic.bed.solid0.velocity = 0.00 0.00 0.00 + +ic.bed.solid0.diameter = constant +ic.bed.solid0.density = constant + +ic.bed.solid0.diameter.constant = 100.0e-6 +ic.bed.solid0.density.constant = 1000.0 + + +#_______________________________________________________________________ +# Boundary Conditions +# +bc.regions = inflow outflow + +bc.inflow = mi +bc.inflow.fluid.volfrac = 1.0 +bc.inflow.fluid.velocity = 0.015 0.0 0.0 + + +bc.outflow = po +bc.outflow.fluid.pressure = 0.0 + +)"); + CHECK(maybe_st.has_value()); +} diff --git a/src/tests/parser/main.cpp b/src/tests/parser/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fc72584f1b1bd7c8bc9414ccee5fd417b547cae5 --- /dev/null +++ b/src/tests/parser/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN // Catch main() - only add to one cpp file +#include "catch2/catch.hpp" diff --git a/subprojects/Catch2 b/subprojects/Catch2 new file mode 160000 index 0000000000000000000000000000000000000000..62609621088df6723e48ea1d164108303511755b --- /dev/null +++ b/subprojects/Catch2 @@ -0,0 +1 @@ +Subproject commit 62609621088df6723e48ea1d164108303511755b diff --git a/subprojects/PEGTL b/subprojects/PEGTL new file mode 160000 index 0000000000000000000000000000000000000000..054e44694f6c8cd24259b70f48cc09d84a9af395 --- /dev/null +++ b/subprojects/PEGTL @@ -0,0 +1 @@ +Subproject commit 054e44694f6c8cd24259b70f48cc09d84a9af395 diff --git a/subprojects/mfix b/subprojects/mfix new file mode 160000 index 0000000000000000000000000000000000000000..e4aaa45a48378bf8f220a359994a42066692f194 --- /dev/null +++ b/subprojects/mfix @@ -0,0 +1 @@ +Subproject commit e4aaa45a48378bf8f220a359994a42066692f194