From 0dce9a07b88ffb84e0028d55f2f554873ec4051d Mon Sep 17 00:00:00 2001 From: Daniel Dunning Date: Tue, 9 Apr 2024 10:31:56 -0600 Subject: [PATCH 01/14] FEATURE: adding evpfft-gpu package to our packages --- .github/workflows/publish-evpfft-gpu.yaml | 16 +++++++++ scripts/build-fierro.sh | 36 ++++++++++++++----- scripts/cmake_build.sh | 4 ++- .../example_evpfft_standalone_inputfile.txt | 6 ++-- .../scripts/build-scripts/build_evpfft.sh | 8 ++--- src/EVPFFT/src/CMakeLists.txt | 13 +++---- 6 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/publish-evpfft-gpu.yaml diff --git a/.github/workflows/publish-evpfft-gpu.yaml b/.github/workflows/publish-evpfft-gpu.yaml new file mode 100644 index 000000000..ec3148ca5 --- /dev/null +++ b/.github/workflows/publish-evpfft-gpu.yaml @@ -0,0 +1,16 @@ +name: 'Publish EVPFFT-GPU' + +on: + push: + paths: + - .conda/evpfft/** + - src/EVPFFT/src/** + - .github/workflows/publish-evpfft-gpu.yaml + workflow_dispatch: + +jobs: + publish: + uses: ./.github/workflows/build-conda-package.yaml + with: + recipe_dir: .conda/evpfft/gpu + secrets: inherit diff --git a/scripts/build-fierro.sh b/scripts/build-fierro.sh index 42b60d177..b82f3ebdd 100755 --- a/scripts/build-fierro.sh +++ b/scripts/build-fierro.sh @@ -6,8 +6,9 @@ show_help() { echo " --kokkos_build_type=. Default is 'serial'" echo " --build_action=. Default is 'full-app'" echo " --machine=. Default is 'linux'" - echo " --build_cores=. Default is set 1" - echo " --heffte_build_type=. Default is set 'fftw'" + echo " --build_cores=. Default is 1" + echo " --build_type=. Default is 'source'" + echo " --heffte_build_type=. Default is 'fftw'" echo " --help: Display this help message" echo " " echo " " @@ -22,6 +23,11 @@ show_help() { echo " install-uncrustify builds and installs uncrustify. Only necessary for developers" echo " fierro Generates CMake files and builds Fierro only (none of the dependencies)." echo " " + echo " --build_type The build type of the applications. Specifies whether we're building from source or using pre-built anaconda libraries" + echo " " + echo " source builds Fierro and libraries from source codes." + echo " anaconda builds Fierro using anaconda pre-built libraries." + echo " " echo " --solver Builds the desired solver to run. The default action is 'explicit'" echo " " echo " all builds both the explicit (non EVPFFT) and implicit solvers. Generally for debugging purposes" @@ -63,12 +69,14 @@ machine="linux" kokkos_build_type="serial" heffte_build_type="fftw" build_cores="1" +fierro_build_type="source" # Define arrays of valid options valid_build_action=("full-app" "set-env" "install-trilinos" "install-hdf5" "install-heffte" "install-uncrustify" "fierro") valid_solver=("all" "explicit" "explicit-evpfft" "explicit-ls-evpfft" "implicit") valid_kokkos_build_types=("serial" "openmp" "pthreads" "cuda" "hip") valid_heffte_build_types=("fftw" "cufft" "rocfft") +valid_fierro_build_types=("source" "anaconda") valid_machines=("darwin" "chicoma" "linux" "mac" "msu") # Parse command line arguments @@ -134,6 +142,16 @@ for arg in "$@"; do return 1 fi ;; + --build_type=*) + option="${arg#*=}" + if [[ " ${valid_fierro_build_types[*]} " == *" $option "* ]]; then + fierro_build_type="$option" + else + echo "Error: Invalid --build_type specified." + show_help + return 1 + fi + ;; --help) show_help return 1 @@ -184,12 +202,14 @@ source setup-env.sh ${machine} ${kokkos_build_type} ${build_cores} # Next, do action based on args if [ "$build_action" = "full-app" ]; then source uncrustify-install.sh - source trilinos-install.sh ${kokkos_build_type} ${machine} - if [ "$solver" = "explicit-evpfft" ] || [ "${solver}" = "explicit-ls-evpfft" ]; then - source hdf5-install.sh - source heffte-install.sh ${heffte_build_type} ${machine} + if [ "$fierro_build_type" = "source" ]; then + source trilinos-install.sh ${kokkos_build_type} ${machine} + if [ "$solver" = "explicit-evpfft" ] || [ "${solver}" = "explicit-ls-evpfft" ]; then + source hdf5-install.sh + source heffte-install.sh ${heffte_build_type} ${machine} + fi fi - source cmake_build.sh ${solver} ${heffte_build_type} ${kokkos_build_type} + source cmake_build.sh ${solver} ${heffte_build_type} ${kokkos_build_type} ${fierro_build_type} elif [ "$build_action" = "install-trilinos" ]; then source trilinos-install.sh ${kokkos_build_type} ${machine} elif [ "$build_action" = "install-hdf5" ]; then @@ -199,7 +219,7 @@ elif [ "$build_action" = "install-heffte" ]; then elif [ "$build_action" = "install-uncrustify" ]; then source uncrustify-install.sh elif [ "$build_action" = "fierro" ]; then - source cmake_build.sh ${solver} ${heffte_build_type} ${kokkos_build_type} + source cmake_build.sh ${solver} ${heffte_build_type} ${kokkos_build_type} ${fierro_build_type} else echo "No build action, only setup the environment." fi diff --git a/scripts/cmake_build.sh b/scripts/cmake_build.sh index 130584a95..fcd4ed051 100644 --- a/scripts/cmake_build.sh +++ b/scripts/cmake_build.sh @@ -3,6 +3,7 @@ solver="${1}" heffte_build_type="${2}" kokkos_build_type="${3}" +fierro_build_type="${4}" #inititialize submodules if they aren't downloaded [ -d "${libdir}/Elements/elements" ] && echo "Elements submodule exists" @@ -22,7 +23,8 @@ else fi # Install Elements -if [ ! -d "${ELEMENTS_INSTALL_DIR}/lib" ]; then +if { [ ! -d "${ELEMENTS_INSTALL_DIR}/lib" ] && [ "$fierro_build_type" = "source" ] ;} +then echo "Installing Elements..." cmake -D CMAKE_INSTALL_PREFIX="$ELEMENTS_INSTALL_DIR" -D Trilinos_DIR="${Trilinos_DIR}" -D Matar_ENABLE_KOKKOS=ON -D Matar_KOKKOS_PACKAGE=Trilinos -B "${ELEMENTS_BUILD_DIR}" -S "${ELEMENTS_SOURCE_DIR}" make -C "${ELEMENTS_BUILD_DIR}" -j${FIERRO_BUILD_CORES} diff --git a/src/EVPFFT/example_input_files/example_evpfft_standalone_inputfile.txt b/src/EVPFFT/example_input_files/example_evpfft_standalone_inputfile.txt index a35728706..b552d6185 100644 --- a/src/EVPFFT/example_input_files/example_evpfft_standalone_inputfile.txt +++ b/src/EVPFFT/example_input_files/example_evpfft_standalone_inputfile.txt @@ -3,12 +3,12 @@ 1 number of phases (nph) 1. 1. 1. RVE dimensions (delt) * name and path of microstructure file (filetext) -/absolute/path/to/random_microstructure_8x8x8.txt +random_microstructure_8x8x8.txt *INFORMATION ABOUT PHASE #1 0 igas(iph) * name and path of single crystal files (filecryspl, filecrysel) (dummy if igas(iph)=1) -/absolute/path/to/example_plastic_parameters.txt -/absolute/path/to/example_elastic_parameters.txt +example_plastic_parameters.txt +example_elastic_parameters.txt *INFORMATION ABOUT TEST CONDITIONS * boundary conditions 0 1 1 iudot | flag for vel.grad. diff --git a/src/EVPFFT/scripts/build-scripts/build_evpfft.sh b/src/EVPFFT/scripts/build-scripts/build_evpfft.sh index e8d849bbc..c2fcfddb2 100644 --- a/src/EVPFFT/scripts/build-scripts/build_evpfft.sh +++ b/src/EVPFFT/scripts/build-scripts/build_evpfft.sh @@ -122,21 +122,21 @@ if [ "$build_fftw" -eq 1 ]; then build_fftw_option="--build_fftw" fi HEFFTE_INSTALL_SCRIPT="$PARENT_DIR/scripts/install-scripts/install_heffte.sh" -source "$HEFFTE_INSTALL_SCRIPT" --heffte_build_type=$heffte_build_type --num_jobs=$num_jobs $build_fftw_option +###source "$HEFFTE_INSTALL_SCRIPT" --heffte_build_type=$heffte_build_type --num_jobs=$num_jobs $build_fftw_option # --------building kokkos KOKKOS_INSTALL_SCRIPT="$PARENT_DIR/scripts/install-scripts/install_kokkos.sh" -source "$KOKKOS_INSTALL_SCRIPT" --kokkos_build_type=$kokkos_build_type --num_jobs=$num_jobs +###source "$KOKKOS_INSTALL_SCRIPT" --kokkos_build_type=$kokkos_build_type --num_jobs=$num_jobs # --------building hdf5 if [ "$build_hdf5" -eq 1 ]; then HDF5_INSTALL_SCRIPT="$PARENT_DIR/scripts/install-scripts/install_hdf5.sh" - source "$HDF5_INSTALL_SCRIPT" --num_jobs=$num_jobs +### source "$HDF5_INSTALL_SCRIPT" --num_jobs=$num_jobs fi # --------building matar MATAR_INSTALL_SCRIPT="$PARENT_DIR/scripts/install-scripts/install_matar.sh" -source "$MATAR_INSTALL_SCRIPT" --kokkos_build_type=$kokkos_build_type --num_jobs=$num_jobs +###source "$MATAR_INSTALL_SCRIPT" --kokkos_build_type=$kokkos_build_type --num_jobs=$num_jobs # --------building EVPFFT EVPFFT_SOURCE_DIR="$PARENT_DIR/src" diff --git a/src/EVPFFT/src/CMakeLists.txt b/src/EVPFFT/src/CMakeLists.txt index 73d548596..026382e6a 100644 --- a/src/EVPFFT/src/CMakeLists.txt +++ b/src/EVPFFT/src/CMakeLists.txt @@ -62,12 +62,13 @@ endif() # HAVE_KOKKOS must be defined for MATAR to build Kokkos types add_definitions(-DHAVE_KOKKOS=1) -if (NOT BUILD_EVPFFT_FIERRO) - if (USE_CUFFT) - find_package(CUDAToolkit REQUIRED) - endif() - find_package(Kokkos REQUIRED) -endif() +#if (NOT BUILD_EVPFFT_FIERRO) + #if (USE_CUFFT) + # find_package(CUDAToolkit REQUIRED) + #endif() +# find_package(Kokkos REQUIRED) +#endif() +find_package(Kokkos REQUIRED) find_package(MPI REQUIRED) find_package(Heffte REQUIRED) # HDF5 From 14184e218afa2fca41fe39aa275b69497b185251 Mon Sep 17 00:00:00 2001 From: Daniel Dunning Date: Tue, 9 Apr 2024 10:58:18 -0600 Subject: [PATCH 02/14] FEATURE: incorrect name on package --- .github/workflows/publish-evpfft-gpu.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-evpfft-gpu.yaml b/.github/workflows/publish-evpfft-gpu.yaml index ec3148ca5..c2e925b58 100644 --- a/.github/workflows/publish-evpfft-gpu.yaml +++ b/.github/workflows/publish-evpfft-gpu.yaml @@ -12,5 +12,5 @@ jobs: publish: uses: ./.github/workflows/build-conda-package.yaml with: - recipe_dir: .conda/evpfft/gpu + recipe_dir: .conda/evpfft/cuda secrets: inherit From 7d018624905913fe0c532dca5bf82985d100169c Mon Sep 17 00:00:00 2001 From: Daniel Dunning Date: Tue, 9 Apr 2024 11:10:12 -0600 Subject: [PATCH 03/14] FEATURE: updated packages --- .conda/evpfft/cuda/meta.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.conda/evpfft/cuda/meta.yaml b/.conda/evpfft/cuda/meta.yaml index 80d01d516..2061e912f 100644 --- a/.conda/evpfft/cuda/meta.yaml +++ b/.conda/evpfft/cuda/meta.yaml @@ -26,13 +26,15 @@ requirements: - cuda-toolkit - openmpi - fierro-heffte-cuda - - hdf5=*=mpi_openmpi_h457a7a6_3 - - fierro-trilinos-cuda # Statically linked against Kokkos CUDA kernels from here. + - hdf5=*=mpi_openmpi_* + - kokkos=4.1 - elements run: - openmpi - fierro-heffte-cuda - - hdf5=*=mpi_openmpi_h457a7a6_3 + - fftw=*=mpi_openmpi_* + - kokkos=4.1 + - hdf5=*=mpi_openmpi_* - elements about: From 926561be03d688940a23308ad6f369edfddee27b Mon Sep 17 00:00:00 2001 From: Daniel Dunning Date: Tue, 9 Apr 2024 11:19:41 -0600 Subject: [PATCH 04/14] FEATURE: updated packages --- .conda/evpfft/cuda/meta.yaml | 1 - .conda/heffte/cuda/meta.yaml | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.conda/evpfft/cuda/meta.yaml b/.conda/evpfft/cuda/meta.yaml index 2061e912f..9143f937c 100644 --- a/.conda/evpfft/cuda/meta.yaml +++ b/.conda/evpfft/cuda/meta.yaml @@ -32,7 +32,6 @@ requirements: run: - openmpi - fierro-heffte-cuda - - fftw=*=mpi_openmpi_* - kokkos=4.1 - hdf5=*=mpi_openmpi_* - elements diff --git a/.conda/heffte/cuda/meta.yaml b/.conda/heffte/cuda/meta.yaml index 92ec25398..096efdedb 100644 --- a/.conda/heffte/cuda/meta.yaml +++ b/.conda/heffte/cuda/meta.yaml @@ -21,7 +21,7 @@ build: requirements: build: - - cmake=3.24 + - cmake >=3.17.0 - {{ compiler('c') }}={{ linux_compiler_version }} # [linux] - {{ compiler('c') }}={{ macos_compiler_version }} # [osx] - {{ compiler('cxx') }}={{ linux_compiler_version }} # [linux] @@ -37,12 +37,12 @@ requirements: - libcufft-dev - libcublas-dev - libcusparse-dev - - openmpi + - openmpi >=4.1.6,<5.0a0 run: - cuda-cudart - libcufft - libcublas - - openmpi + - openmpi >=4.1.6,<5.0a0 about: home: https://github.com/lanl/Fierro From ef7aec3ae3cc2a48d2c3132abfdab3cda62ad71a Mon Sep 17 00:00:00 2001 From: Daniel Dunning <39738037+djdunning@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:39:03 -0600 Subject: [PATCH 05/14] Fixing packages for evpfft-dev --- .conda/evpfft-dev/cpu/meta.yaml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.conda/evpfft-dev/cpu/meta.yaml b/.conda/evpfft-dev/cpu/meta.yaml index e59bb6636..ec8fc7452 100644 --- a/.conda/evpfft-dev/cpu/meta.yaml +++ b/.conda/evpfft-dev/cpu/meta.yaml @@ -14,9 +14,9 @@ build: - PLATFORM={{ target_platform }} requirements: - run: - - {{ compiler('c') }}={{ linux_compiler_version }} # [linux] - - {{ compiler('c') }}={{ macos_compiler_version }} # [osx] + host: + # - {{ compiler('c') }}={{ linux_compiler_version }} # [linux] + # - {{ compiler('c') }}={{ macos_compiler_version }} # [osx] - {{ compiler('cxx') }}={{ linux_compiler_version }} # [linux] - {{ compiler('cxx') }}={{ macos_compiler_version }} # [osx] - openmpi @@ -25,6 +25,17 @@ requirements: - kokkos=4.1 - hdf5=*=mpi_openmpi_* - elements + run: + # - {{ compiler('c') }}={{ linux_compiler_version }} # [linux] + # - {{ compiler('c') }}={{ macos_compiler_version }} # [osx] + # - {{ compiler('cxx') }}={{ linux_compiler_version }} # [linux] + # - {{ compiler('cxx') }}={{ macos_compiler_version }} # [osx] + - openmpi + - fierro-heffte + - fftw=*=mpi_openmpi_* + - kokkos=4.1 + - hdf5=*=mpi_openmpi_* + - elements about: home: https://github.com/lanl/Fierro From c8fdb6aa5054622aee1198c769251cb990069853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAdrian-Diaz=E2=80=9D?= <“adriandiaz1117@gmail.com”> Date: Thu, 14 Mar 2024 14:09:26 -0600 Subject: [PATCH 06/14] ENH: abaqus inp condition read interface --- src/Parallel-Solvers/FEA_Module.h | 2 + .../FEA_Module_Elasticity.cpp | 420 ++++++++++++++++++ .../FEA_Module_Elasticity.h | 4 +- .../Implicit-Lagrange/Implicit_Solver.cpp | 17 +- 4 files changed, 440 insertions(+), 3 deletions(-) diff --git a/src/Parallel-Solvers/FEA_Module.h b/src/Parallel-Solvers/FEA_Module.h index c1dacd9f2..9bd28d854 100644 --- a/src/Parallel-Solvers/FEA_Module.h +++ b/src/Parallel-Solvers/FEA_Module.h @@ -144,6 +144,8 @@ class FEA_Module virtual void read_conditions_ansys_dat(std::ifstream* in, std::streampos before_condition_header) {} + virtual void read_conditions_abaqus_inp(std::ifstream* in, std::streampos before_condition_header) {} + virtual void linear_solver_parameters() {} virtual void comm_variables(Teuchos::RCP zp) {} diff --git a/src/Parallel-Solvers/Implicit-Lagrange/FEA_Physics_Modules/FEA_Module_Elasticity.cpp b/src/Parallel-Solvers/Implicit-Lagrange/FEA_Physics_Modules/FEA_Module_Elasticity.cpp index b1c414ec9..8ea1d9a62 100644 --- a/src/Parallel-Solvers/Implicit-Lagrange/FEA_Physics_Modules/FEA_Module_Elasticity.cpp +++ b/src/Parallel-Solvers/Implicit-Lagrange/FEA_Physics_Modules/FEA_Module_Elasticity.cpp @@ -597,6 +597,426 @@ void FEA_Module_Elasticity::read_conditions_ansys_dat(std::ifstream *in, std::st if(myrank == 0) in->close(); +} // end read_conditions_ansys_dat + +/* ---------------------------------------------------------------------- + Read ANSYS dat format mesh file +------------------------------------------------------------------------- */ +void FEA_Module_Elasticity::read_conditions_abaqus_inp(std::ifstream *in, std::streampos before_condition_header){ + + char ch; + int num_dim = simparam->num_dims; + int buffer_lines = 1000; + int max_word = 30; + Input_Options input_options = simparam->input_options.value(); + int p_order = input_options.p_order; + real_t unit_scaling = input_options.unit_scaling; + int local_node_index, current_column_index; + size_t strain_count; + std::string skip_line, read_line, substring, token; + std::stringstream line_parse, line_parse2; + CArrayKokkos read_buffer; + CArrayKokkos read_buffer_indices; + int buffer_loop, buffer_iteration, buffer_iterations, scan_loop, nodes_per_element, words_per_line; + size_t read_index_start, node_rid, elem_gid; + LO local_dof_id; + GO node_gid; + real_t dof_value; + host_vec_array node_densities; + //Nodes_Per_Element_Type = elements::elem_types::Nodes_Per_Element_Type; + + //initialize boundary condition storage structures + init_boundaries(); + + //task 0 reads file, it should be open by now due to Implicit Solver mesh read in + + //ANSYS dat file doesn't always correctly specify total number of nodes, which is needed for the node map. + //First pass reads in node section to determine the maximum number of nodes, second pass distributes node data + //The elements section header does specify element count + + //revert file position to before first condition zone + if(myrank==0){ + in->seekg(before_condition_header); + } + + //prompts all MPI ranks to expect more broadcasts + GO dof_count; + bool searching_for_conditions = true; + bool found_no_conditions = true; + int zone_condition_type = NONE; + bool zero_displacement = false; + bool assign_flag; + //skip lines at the top with nonessential info; stop skipping when "Nodes for the whole assembly" string is reached + while (searching_for_conditions) { + if(myrank==0){ + //reset variables + zone_condition_type = NONE; + while(searching_for_conditions&&in->good()){ + getline(*in, skip_line); + //std::cout << skip_line << std::endl; + line_parse.clear(); + line_parse.str(skip_line); + //stop when the NODES= string is reached + while (!line_parse.eof()){ + line_parse >> substring; + //std::cout << substring << std::endl; + if(!substring.compare("Supports")){ + //std::cout << "FOUND BC ZONE" << std::endl; + searching_for_conditions = found_no_conditions = false; + zone_condition_type = DISPLACEMENT_CONDITION; + } + if(!substring.compare("Pressure")){ + + //std::cout << "FOUND PRESSURE ZONE" << std::endl; + searching_for_conditions = found_no_conditions = false; + zone_condition_type = SURFACE_LOADING_CONDITION; + } + } //while + }//while + } + + //broadcast zone flags + MPI_Bcast(&zone_condition_type,1,MPI_INT,0,world); + //perform readin strategy according to zone type + if(zone_condition_type==DISPLACEMENT_CONDITION){ + node_specified_bcs = true; + bool per_node_flag = false; + if(myrank==0){ + getline(*in, read_line); + std::cout << read_line << std::endl; + line_parse.clear(); + line_parse.str(read_line); + line_parse >> substring; + //parse boundary condition specifics out of jumble of comma delimited entries + line_parse2.clear(); + line_parse2.str(substring); + while(line_parse2.good()){ + getline(line_parse2, token, ','); + if(!token.compare("FIXEDSU")){ + nonzero_bc_flag = false; + } + if(!token.compare("NODE")){ + per_node_flag = true; + } + } + //read number of nodes/dof in boundary condition zone + line_parse >> dof_count; + //skip 1 line + getline(*in, read_line); + } + //broadcast number of fixed support conditions to read in (global ids) + + MPI_Bcast(&dof_count,1,MPI_LONG_LONG_INT,0,world); + + //calculate buffer iterations to read number of lines + buffer_iterations = dof_count/buffer_lines; + + if(dof_count%buffer_lines!=0) buffer_iterations++; + read_index_start = 0; + + //allocate read buffer + read_buffer_indices = CArrayKokkos(buffer_lines); + //read global indices being fixed on rank zero then broadcast buffer until list is complete + for(buffer_iteration = 0; buffer_iteration < buffer_iterations; buffer_iteration++){ + //pack buffer on rank 0 + if(myrank==0&&buffer_iteration> read_buffer_indices(buffer_loop); + read_buffer_indices(buffer_loop); + } + } + else if(myrank==0){ + buffer_loop=0; + while(buffer_iteration*buffer_lines+buffer_loop < dof_count) { + *in >> read_buffer_indices(buffer_loop); + read_buffer_indices(buffer_loop); + buffer_loop++; + } + } + + //broadcast buffer to all ranks; each rank will determine which nodes in the buffer belong + MPI_Bcast(read_buffer_indices.pointer(),buffer_lines,MPI_LONG_LONG_INT,0,world); + //broadcast how many nodes were read into this buffer iteration + MPI_Bcast(&buffer_loop,1,MPI_INT,0,world); + + //debug_print + //std::cout << "NODE BUFFER LOOP IS: " << buffer_loop << std::endl; + //for(int iprint=0; iprint < buffer_loop; iprint++) + //std::cout<<"buffer packing: " << read_buffer_indices(iprint) << std::endl; + //return; + + //determine which data to store in the swage mesh members (the local node data) + //loop through read buffer + for(scan_loop = 0; scan_loop < buffer_loop; scan_loop++){ + node_gid = read_buffer_indices(scan_loop)-1; //read indices are base 1, we need base 0 + //let map decide if this node id belongs locally; if yes store data + if(all_node_map->isNodeGlobalElement(node_gid)){ + //set local node index in this mpi rank + local_node_index = all_node_map->getLocalElement(node_gid); + if(map->isNodeGlobalElement(node_gid)){ + Number_DOF_BCS+=num_dim; + } + if(nonzero_bc_flag){ + + } + else{ + local_dof_id = num_dim*local_node_index; + Node_DOF_Boundary_Condition_Type(local_dof_id) = DISPLACEMENT_CONDITION; + Node_DOF_Displacement_Boundary_Conditions(local_dof_id) = 0; + Node_DOF_Boundary_Condition_Type(local_dof_id + 1) = DISPLACEMENT_CONDITION; + Node_DOF_Displacement_Boundary_Conditions(local_dof_id + 1) = 0; + if(num_dim==3){ + Node_DOF_Boundary_Condition_Type(local_dof_id + 2) = DISPLACEMENT_CONDITION; + Node_DOF_Displacement_Boundary_Conditions(local_dof_id + 2) = 0; + } + } + } + } + read_index_start+=buffer_lines; + } + } + + if(zone_condition_type==SURFACE_LOADING_CONDITION){ + LO local_patch_index; + LO boundary_set_npatches = 0; + bool look_at_end = false; + CArray Surface_Nodes; + std::streampos last_zone_ending_position; + //grow structures for loading condition storage + //debug print + std::cout << "BOUNDARY INDEX FOR LOADING CONDITION " << num_boundary_conditions << " FORCE SET INDEX "<< num_surface_force_sets << std::endl; + if(num_boundary_conditions + 1>max_boundary_sets) grow_boundary_sets(num_boundary_conditions+1); + num_boundary_conditions++; + if(num_surface_force_sets + 1>max_load_boundary_sets) grow_loading_condition_sets(num_surface_force_sets+1); + num_surface_force_sets++; + + GO num_patches; + real_t force_density[3]; + force_density[0] = force_density[1] = force_density[2] = 0; + if(myrank == 0){ + getline(*in, read_line); + std::cout << read_line << std::endl; + line_parse.clear(); + line_parse.str(read_line); + line_parse >> substring; + //parse boundary condition specifics out of jumble of comma delimited entries + line_parse2.clear(); + line_parse2.str(substring); + //this first token should be the word local, otherwise the traction is printed at file end + getline(line_parse2, token, ','); + if(token.compare("local")){ + look_at_end = true; + } + + if(!look_at_end){ + getline(line_parse2, token, ','); + force_density[0] = std::stod(token); + getline(line_parse2, token, ','); + force_density[1] = std::stod(token); + getline(line_parse2, token, ','); + force_density[2] = std::stod(token); + + //skip 2 lines + getline(*in, read_line); + getline(*in, read_line); + } + //read number of surface patches + getline(*in, read_line); + std::cout << read_line << std::endl; + line_parse.clear(); + line_parse.str(read_line); + line_parse >> substring; + //parse boundary condition specifics out of jumble of comma delimited entries + line_parse2.clear(); + line_parse2.str(substring); + while(line_parse2.good()){ + getline(line_parse2, token, ','); + } + //number of patches should be last read token + //element count should be the last token read in + num_patches = std::stoi(token); + + //std::cout << " NUMBER OF BOUNDARY PATCHES "<< num_patches << std::endl; + //skip 1 more line + getline(*in, read_line); + } + + //broadcast number of element surface patches subject to force density + MPI_Bcast(&look_at_end,1,MPI_CXX_BOOL,0,world); + + if(look_at_end){ + Boundary_Condition_Type_List(num_boundary_conditions-1) = SURFACE_PRESSURE_CONDITION; + } + else{ + Boundary_Condition_Type_List(num_boundary_conditions-1) = SURFACE_LOADING_CONDITION; + } + + + //broadcast number of element surface patches subject to force density + MPI_Bcast(&num_patches,1,MPI_LONG_LONG_INT,0,world); + + std::cout << "LOAD PATCHES TO READ " << num_patches << std::endl; + int nodes_per_patch; + //select nodes per patch based on element type + if(Implicit_Solver_Pointer_->Element_Types(0) == elements::elem_types::Hex8){ + nodes_per_patch = 4; + } + if(Implicit_Solver_Pointer_->Element_Types(0) == elements::elem_types::Hex20){ + nodes_per_patch = 8; + } + + //calculate buffer iterations to read number of lines + buffer_iterations = num_patches/buffer_lines; + + if(num_patches%buffer_lines!=0) buffer_iterations++; + read_index_start = 0; + //allocate read buffer + read_buffer_indices = CArrayKokkos(buffer_lines,nodes_per_patch); + int non_node_entries = 5; + words_per_line = nodes_per_patch + non_node_entries; + + for(buffer_iteration = 0; buffer_iteration < buffer_iterations; buffer_iteration++){ + //pack buffer on rank 0 + if(myrank==0&&buffer_iteration> substring; + //debug print + //std::cout<<" "<< substring <non_node_entries-1){ + read_buffer_indices(buffer_loop,iword-non_node_entries) = std::stoi(substring)-1; + } + } + } + } + else if(myrank==0){ + buffer_loop=0; + while(buffer_iteration*buffer_lines+buffer_loop < num_patches) { + getline(*in,read_line); + line_parse.clear(); + line_parse.str(read_line); + for(int iword = 0; iword < words_per_line; iword++){ + //read portions of the line into the substring variable + line_parse >> substring; + //assign the substring variable as a word of the read buffer + if(iword>non_node_entries-1){ + read_buffer_indices(buffer_loop,iword-non_node_entries) = std::stoi(substring)-1; //make base 0, file has base 1 + } + } + buffer_loop++; + } + + } + + //broadcast buffer to all ranks; each rank will determine which nodes in the buffer belong + MPI_Bcast(read_buffer_indices.pointer(),buffer_lines*nodes_per_patch,MPI_LONG_LONG_INT,0,world); + //broadcast how many nodes were read into this buffer iteration + MPI_Bcast(&buffer_loop,1,MPI_INT,0,world); + + //determine which data to store in the swage mesh members (the local node data) + //loop through read buffer + //std::cout << "BUFFER LOOP IS " << buffer_loop << " ASSIGNED ON RANK " << myrank << std::endl; + int belong_count; + for(scan_loop = 0; scan_loop < buffer_loop; scan_loop++){ + belong_count = 0; + //judge if this patch could be relevant to this MPI rank + //all nodes of the patch must belong to the local + ghost set of nodes + for(int inode = 0; inode < nodes_per_patch; inode++){ + node_gid = read_buffer_indices(scan_loop, inode); + if(map->isNodeGlobalElement(node_gid)){ + belong_count++; + } + } + if(belong_count){ + //construct patch object and look for patch index; the assign patch index to the new loading condition set + Surface_Nodes = CArray(nodes_per_patch); + for(int inode = 0; inode < nodes_per_patch; inode++){ + Surface_Nodes(inode) = read_buffer_indices(scan_loop, inode); + } + Node_Combination temp(Surface_Nodes); + //debug print + //std::cout << "PATCH NODES " << boundary_set_npatches +1 << " " << Surface_Nodes(0) << " " << Surface_Nodes(1) << " " << Surface_Nodes(2) << " " << Surface_Nodes(3) << " ASSIGNED ON RANK " << myrank << std::endl; + //construct Node Combination object for this surface + local_patch_index = Implicit_Solver_Pointer_->boundary_patch_to_index[temp]; + //debug print + //std::cout << "MAPPED PATCH NODES " << boundary_set_npatches +1 << " " << Boundary_Patches(local_patch_index).node_set(0) << " " << Boundary_Patches(local_patch_index).node_set(1) << " " << Boundary_Patches(local_patch_index).node_set(2) << " " << Boundary_Patches(local_patch_index).node_set(3) << " ASSIGNED ON RANK " << myrank << std::endl; + Boundary_Condition_Patches(num_boundary_conditions-1,boundary_set_npatches++) = local_patch_index; + //debug print + //std::cout << "PATCH INDEX " << local_patch_index << " ASSIGNED ON RANK " << myrank << std::endl; + } + //find patch id associated with node combination + } + read_index_start+=buffer_lines; + } + NBoundary_Condition_Patches(num_boundary_conditions-1) = boundary_set_npatches; + + //get surface pressure from the end of the file + if(myrank==0){ + if(look_at_end){ + //save file position in case there other conditions to read in afterwards + last_zone_ending_position = in->tellg(); + bool found_pressure = false; + real_t pressure; + while(in->good()){ + getline(*in, read_line); + //std::cout << read_line << std::endl; + line_parse.clear(); + line_parse.str(read_line); + line_parse >> substring; + //parse for surface pressure token "sf" then obtain pressure value + line_parse2.clear(); + line_parse2.str(substring); + while(line_parse2.good()){ + getline(line_parse2, token, ','); + if(!token.compare("sf")){ + found_pressure = true; + } + } + if(found_pressure){ + //last token read in from the line should be the pressure + pressure = std::stod(token); + break; + } + } + force_density[0] = pressure; + //reset file position + in->seekg(last_zone_ending_position); + } + } + + //broadcast surface force density + MPI_Bcast(&force_density,3,MPI_DOUBLE,0,world); + + //std::cout << " FORCE DENSITY "<< force_density[0] << std::endl; + Boundary_Surface_Force_Densities(num_surface_force_sets-1,0) = force_density[0]; + Boundary_Surface_Force_Densities(num_surface_force_sets-1,1) = force_density[1]; + Boundary_Surface_Force_Densities(num_surface_force_sets-1,2) = force_density[2]; + } + + if(myrank==0){ + //previous search on rank 0 for boundary condition keywords failed if search is still true + if(found_no_conditions){ + std::cout << "FILE FORMAT ERROR" << std::endl; + } + //check if there is yet more text to try reading for more boundary condition keywords + searching_for_conditions = in->good(); + } + + MPI_Bcast(&searching_for_conditions,1,MPI_CXX_BOOL,0,world); + } //All rank while loop + + //close file + if(myrank == 0) in->close(); + + } // end read_conditions_ansys_dat /* ---------------------------------------------------------------------------- diff --git a/src/Parallel-Solvers/Implicit-Lagrange/FEA_Physics_Modules/FEA_Module_Elasticity.h b/src/Parallel-Solvers/Implicit-Lagrange/FEA_Physics_Modules/FEA_Module_Elasticity.h index cc6f6fcb7..d7d7a8b90 100644 --- a/src/Parallel-Solvers/Implicit-Lagrange/FEA_Physics_Modules/FEA_Module_Elasticity.h +++ b/src/Parallel-Solvers/Implicit-Lagrange/FEA_Physics_Modules/FEA_Module_Elasticity.h @@ -135,7 +135,9 @@ class FEA_Module_Elasticity: public FEA_Module{ void Gradient_Body_Term(size_t ielem, real_t density, real_t *forces); - void read_conditions_ansys_dat(std::ifstream *in, std::streampos before_condition_header); + void read_conditions_ansys_dat(std::ifstream *in, std::streampos before_condition_header); //ANSYS .dat import of specified load and boundary conditions + + void read_conditions_abaqus_inp(std::ifstream *in, std::streampos before_condition_header); //ABAQUS .inp import of specified load and boundary conditions //interfaces between user input and creating data structures for bcs void generate_bcs(); diff --git a/src/Parallel-Solvers/Implicit-Lagrange/Implicit_Solver.cpp b/src/Parallel-Solvers/Implicit-Lagrange/Implicit_Solver.cpp index f6b04873f..3ffb795db 100644 --- a/src/Parallel-Solvers/Implicit-Lagrange/Implicit_Solver.cpp +++ b/src/Parallel-Solvers/Implicit-Lagrange/Implicit_Solver.cpp @@ -219,8 +219,21 @@ void Implicit_Solver::run(){ //update set options for next FEA module in loop with synchronized set options in solver's simparam class FEA_MODULE_TYPE m_type = simparam.fea_module_parameters[imodule]->type; - if(fea_module_must_read.find(m_type) != fea_module_must_read.end()){ - fea_modules[imodule]->read_conditions_ansys_dat(in, before_condition_header); + if (simparam.input_options.has_value()) { + const Input_Options& input_options = simparam.input_options.value(); + if(fea_module_must_read.find(m_type) != fea_module_must_read.end()){ + switch (input_options.mesh_file_format) { + case MESH_FORMAT::ansys_dat: + fea_modules[imodule]->read_conditions_ansys_dat(in, before_condition_header); + break; + case MESH_FORMAT::abaqus_inp: + fea_modules[imodule]->read_conditions_abaqus_inp(in, before_condition_header); + break; + default: + *fos << "ERROR: MESH FILE FORMAT DOESN'T SUPPORT CONDITION READ" << std::endl; + exit_solver(0); + } + } } else{ fea_modules[imodule]->init_boundaries(); From 0a52c215a33ca5df5847528467fd1586842b8845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAdrian-Diaz=E2=80=9D?= <“adriandiaz1117@gmail.com”> Date: Thu, 14 Mar 2024 18:51:44 -0600 Subject: [PATCH 07/14] ENH: LC + BC input error control --- .../Implicit-Lagrange/Implicit_Solver.cpp | 15 +++++++++++---- .../FEA_Module/FEA_Module_Parameters.h | 2 +- .../FEA_Module/Inertial_Parameters.h | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Parallel-Solvers/Implicit-Lagrange/Implicit_Solver.cpp b/src/Parallel-Solvers/Implicit-Lagrange/Implicit_Solver.cpp index 3ffb795db..3f431a908 100644 --- a/src/Parallel-Solvers/Implicit-Lagrange/Implicit_Solver.cpp +++ b/src/Parallel-Solvers/Implicit-Lagrange/Implicit_Solver.cpp @@ -219,9 +219,12 @@ void Implicit_Solver::run(){ //update set options for next FEA module in loop with synchronized set options in solver's simparam class FEA_MODULE_TYPE m_type = simparam.fea_module_parameters[imodule]->type; - if (simparam.input_options.has_value()) { - const Input_Options& input_options = simparam.input_options.value(); - if(fea_module_must_read.find(m_type) != fea_module_must_read.end()){ + bool yaml_supplied_bcs = (bool)(simparam.fea_module_parameters[imodule]->boundary_conditions.size()); + bool yaml_supplied_lcs = (bool)(simparam.fea_module_parameters[imodule]->loading_conditions.size()); + bool yaml_supplied_no_conditions = !yaml_supplied_bcs && !yaml_supplied_lcs; + if(fea_module_must_read.find(m_type) != fea_module_must_read.end() && yaml_supplied_no_conditions){ + if (simparam.input_options.has_value()) { + const Input_Options& input_options = simparam.input_options.value(); switch (input_options.mesh_file_format) { case MESH_FORMAT::ansys_dat: fea_modules[imodule]->read_conditions_ansys_dat(in, before_condition_header); @@ -235,7 +238,7 @@ void Implicit_Solver::run(){ } } } - else{ + else if(yaml_supplied_bcs&&yaml_supplied_lcs){ fea_modules[imodule]->init_boundaries(); //set boundary conditions for FEA modules @@ -244,6 +247,10 @@ void Implicit_Solver::run(){ //set applied loading conditions for FEA modules fea_modules[imodule]->generate_applied_loads(); } + else if(simparam.fea_module_parameters[imodule]->requires_conditions){ + *fos << "ERROR: FEA MODULE " << imodule << " WAS NOT ASSIGNED EITHER LOADING OR BOUNDARY CONDTIONS" << std::endl; + exit_solver(0); + } } //check for errors in the yaml input and exit if any found with an error message diff --git a/src/Parallel-Solvers/Simulation_Parameters/FEA_Module/FEA_Module_Parameters.h b/src/Parallel-Solvers/Simulation_Parameters/FEA_Module/FEA_Module_Parameters.h index 43a46fa15..1f2d8d634 100644 --- a/src/Parallel-Solvers/Simulation_Parameters/FEA_Module/FEA_Module_Parameters.h +++ b/src/Parallel-Solvers/Simulation_Parameters/FEA_Module/FEA_Module_Parameters.h @@ -29,7 +29,7 @@ struct FEA_Module_Parameters size_t material_id; std::vector boundary_conditions; std::vector> loading_conditions; - + bool requires_conditions = true; // Non-serialized Fields DCArrayKokkos loading; DCArrayKokkos boundary; diff --git a/src/Parallel-Solvers/Simulation_Parameters/FEA_Module/Inertial_Parameters.h b/src/Parallel-Solvers/Simulation_Parameters/FEA_Module/Inertial_Parameters.h index d2c28a83a..40a59e4f4 100644 --- a/src/Parallel-Solvers/Simulation_Parameters/FEA_Module/Inertial_Parameters.h +++ b/src/Parallel-Solvers/Simulation_Parameters/FEA_Module/Inertial_Parameters.h @@ -15,6 +15,7 @@ struct Inertial_Parameters std::vector moment_of_inertia_center {0.0, 0.0, 0.0}; void derive() { + requires_conditions = false; enable_inertia_center = std::vector { inertia_center_x.has_value(), inertia_center_y.has_value(), From f3933b99a9e3afdba0ef64cc71081fe7b226783d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAdrian-Diaz=E2=80=9D?= <“adriandiaz1117@gmail.com”> Date: Tue, 19 Mar 2024 10:20:38 -0600 Subject: [PATCH 08/14] normalize objectives --- .../Heat_Capacity_Potential_Minimize.h | 10 ++++++---- .../Topology_Optimization/Mass_Objective.h | 14 ++++++++++++-- .../Topology_Optimization/Strain_Energy_Minimize.h | 8 ++++++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Heat_Capacity_Potential_Minimize.h b/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Heat_Capacity_Potential_Minimize.h index 7156284a8..adbab98ce 100644 --- a/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Heat_Capacity_Potential_Minimize.h +++ b/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Heat_Capacity_Potential_Minimize.h @@ -92,6 +92,7 @@ class HeatCapacityPotentialMinimize_TopOpt : public ROL::Objective { ROL::Ptr ROL_Temperatures; ROL::Ptr ROL_Gradients; Teuchos::RCP all_node_temperatures_distributed_temp; + real_t initial_heat_capacity_potential; bool useLC_; // Use linear form of compliance. Otherwise use quadratic form. @@ -123,6 +124,7 @@ class HeatCapacityPotentialMinimize_TopOpt : public ROL::Objective { ROL_Temperatures = ROL::makePtr(FEM_->node_temperatures_distributed); real_t current_heat_capacity_potential = -ROL_Temperatures->dot(*ROL_Heat); + initial_heat_capacity_potential = current_heat_capacity_potential; std::cout.precision(10); if(FEM_->myrank==0) std::cout << "INITIAL HEAT CAPACITY POTENTIAL " << current_heat_capacity_potential << std::endl; @@ -227,10 +229,10 @@ class HeatCapacityPotentialMinimize_TopOpt : public ROL::Objective { real_t current_heat_capacity_potential = -ROL_Temperatures->dot(*ROL_Heat); std::cout.precision(10); if(FEM_->myrank==0) - std::cout << "CURRENT HEAT CAPACITY POTENTIAL " << current_heat_capacity_potential << std::endl; + std::cout << "CURRENT NORMALIZED HEAT CAPACITY POTENTIAL " << current_heat_capacity_potential/initial_heat_capacity_potential << std::endl; //std::cout << "Ended obj value on task " <myrank << std::endl; - return current_heat_capacity_potential; + return current_heat_capacity_potential/initial_heat_capacity_potential; } //void gradient_1( ROL::Vector &g, const ROL::Vector &u, const ROL::Vector &z, real_t &tol ) { @@ -263,7 +265,7 @@ class HeatCapacityPotentialMinimize_TopOpt : public ROL::Objective { if(nodal_density_flag_){ FEM_->compute_adjoint_gradients(design_densities, objective_gradients); - gp->scale(-1); + gp->scale(-1/initial_heat_capacity_potential); //debug print of gradient //std::ostream &out = std::cout; //Teuchos::RCP fos = Teuchos::fancyOStream(Teuchos::rcpFromRef(out)); @@ -316,7 +318,7 @@ class HeatCapacityPotentialMinimize_TopOpt : public ROL::Objective { const_host_vec_array direction_vector = vp->getLocalView (Tpetra::Access::ReadOnly); FEM_->compute_adjoint_hessian_vec(design_densities, objective_hessvec, vp); - hvp->scale(-1); + hvp->scale(-1/initial_heat_capacity_potential); //if(FEM_->myrank==0) //std::cout << "hessvec" << std::endl; //vp->describe(*fos,Teuchos::VERB_EXTREME); diff --git a/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Mass_Objective.h b/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Mass_Objective.h index 1a3c6605b..c982d2848 100644 --- a/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Mass_Objective.h +++ b/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Mass_Objective.h @@ -89,6 +89,7 @@ class MassObjective_TopOpt : public ROL::Objective { FEA_Module_Inertial *FEM_; ROL::Ptr ROL_Element_Masses; + real_t initial_mass; bool useLC_; // Use linear form of compliance. Otherwise use quadratic form. @@ -112,6 +113,14 @@ class MassObjective_TopOpt : public ROL::Objective { last_comm_step = last_solve_step = -1; current_step = 0; ROL_Element_Masses = ROL::makePtr(FEM_->Global_Element_Masses); + const_host_vec_array design_densities = FEM_->all_node_densities_distributed->getLocalView (Tpetra::Access::ReadOnly); + FEM_->compute_element_masses(design_densities,false); + + //sum per element results across all MPI ranks + ROL::Elementwise::ReductionSum sumreduc; + initial_mass = ROL_Element_Masses->reduce(sumreduc); + //debug print + std::cout << "INITIAL SYSTEM MASS: " << initial_mass << std::endl; } void update(const ROL::Vector &z, ROL::UpdateType type, int iter = -1 ) { @@ -146,8 +155,8 @@ class MassObjective_TopOpt : public ROL::Objective { ROL::Elementwise::ReductionSum sumreduc; c = ROL_Element_Masses->reduce(sumreduc); //debug print - std::cout << "SYSTEM MASS: " << c << std::endl; - return c; + std::cout << "NORMALIZED SYSTEM MASS: " << c/initial_mass << std::endl; + return c/initial_mass; } //void gradient_1( ROL::Vector &g, const ROL::Vector &u, const ROL::Vector &z, real_t &tol ) { @@ -185,6 +194,7 @@ class MassObjective_TopOpt : public ROL::Objective { for(int ig = 0; ig < rnum_elem; ig++) objective_gradients(ig,0) = element_volumes(ig,0); } + gp->scale(1/initial_mass); std::cout << "Objective Gradient called"<< std::endl; //debug print of design variables //std::ostream &out = std::cout; diff --git a/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Strain_Energy_Minimize.h b/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Strain_Energy_Minimize.h index 191df2c37..3e65ac10e 100644 --- a/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Strain_Energy_Minimize.h +++ b/src/Parallel-Solvers/Implicit-Lagrange/Topology_Optimization/Strain_Energy_Minimize.h @@ -93,6 +93,7 @@ class StrainEnergyMinimize_TopOpt : public ROL::Objective { ROL::Ptr ROL_Displacements; ROL::Ptr ROL_Gradients; Teuchos::RCP all_node_displacements_distributed_temp; + real_t initial_strain_energy; bool useLC_; // Use linear form of compliance. Otherwise use quadratic form. @@ -124,6 +125,7 @@ class StrainEnergyMinimize_TopOpt : public ROL::Objective { ROL_Displacements = ROL::makePtr(FEM_->node_displacements_distributed); real_t current_strain_energy = ROL_Displacements->dot(*ROL_Force)/2; + initial_strain_energy = current_strain_energy; std::cout.precision(10); if(FEM_->myrank==0) std::cout << "INITIAL STRAIN ENERGY " << current_strain_energy << std::endl; @@ -228,10 +230,10 @@ class StrainEnergyMinimize_TopOpt : public ROL::Objective { real_t current_strain_energy = ROL_Displacements->dot(*ROL_Force)/2; std::cout.precision(10); if(FEM_->myrank==0) - std::cout << "CURRENT STRAIN ENERGY " << current_strain_energy << std::endl; + std::cout << "CURRENT NORMALIZED STRAIN ENERGY " << current_strain_energy/initial_strain_energy << std::endl; //std::cout << "Ended obj value on task " <myrank << std::endl; - return current_strain_energy; + return current_strain_energy/initial_strain_energy; } //void gradient_1( ROL::Vector &g, const ROL::Vector &u, const ROL::Vector &z, real_t &tol ) { @@ -263,6 +265,7 @@ class StrainEnergyMinimize_TopOpt : public ROL::Objective { const_host_vec_array design_densities = zp->getLocalView (Tpetra::Access::ReadOnly); FEM_->compute_adjoint_gradients(design_densities, objective_gradients); + gp->scale(1/initial_strain_energy); //debug print of gradient //std::ostream &out = std::cout; //Teuchos::RCP fos = Teuchos::fancyOStream(Teuchos::rcpFromRef(out)); @@ -305,6 +308,7 @@ class StrainEnergyMinimize_TopOpt : public ROL::Objective { const_host_vec_array direction_vector = vp->getLocalView (Tpetra::Access::ReadOnly); FEM_->compute_adjoint_hessian_vec(design_densities, objective_hessvec, vp); + hvp->scale(1/initial_strain_energy); //if(FEM_->myrank==0) //std::cout << "hessvec" << std::endl; //vp->describe(*fos,Teuchos::VERB_EXTREME); From 2bfe64233aa186c626c193a058af6e8e2afb6cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAdrian-Diaz=E2=80=9D?= <“adriandiaz1117@gmail.com”> Date: Wed, 20 Mar 2024 18:59:53 -0600 Subject: [PATCH 09/14] ENH: yaml interface for objective region --- .../Simulation_Parameters/Optimization_Options.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Parallel-Solvers/Simulation_Parameters/Optimization_Options.h b/src/Parallel-Solvers/Simulation_Parameters/Optimization_Options.h index fedf07644..3b783b551 100644 --- a/src/Parallel-Solvers/Simulation_Parameters/Optimization_Options.h +++ b/src/Parallel-Solvers/Simulation_Parameters/Optimization_Options.h @@ -99,6 +99,8 @@ struct Optimization_Options: Yaml::DerivedFields { std::vector constraints; std::vector volume_bound_constraints; DCArrayKokkos optimization_bound_constraint_volumes; + std::vector objective_regions; + DCArrayKokkos optimization_objective_regions; bool method_of_moving_asymptotes = false; double simp_penalty_power = 3.0; bool thick_condition_boundary = true; @@ -118,6 +120,9 @@ struct Optimization_Options: Yaml::DerivedFields { if(volume_bound_constraints.size()>=1){ mtr::from_vector(optimization_bound_constraint_volumes, volume_bound_constraints); } + if(objective_regions.size()>=1){ + mtr::from_vector(optimization_objective_regions, objective_regions); + } } }; YAML_ADD_REQUIRED_FIELDS_FOR(Optimization_Options, @@ -125,7 +130,7 @@ YAML_ADD_REQUIRED_FIELDS_FOR(Optimization_Options, ) IMPL_YAML_SERIALIZABLE_FOR(Optimization_Options, optimization_process, optimization_objective, - constraints, method_of_moving_asymptotes, volume_bound_constraints, + constraints, method_of_moving_asymptotes, volume_bound_constraints, objective_regions, simp_penalty_power, density_epsilon, thick_condition_boundary, optimization_output_freq, density_filter, minimum_density, maximum_density, multi_objective_modules, multi_objective_structure, density_filter, retain_outer_shell, From 985af45f89dafe580a0c5253591e14b1bdd265b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAdrian-Diaz=E2=80=9D?= <“adriandiaz1117@gmail.com”> Date: Mon, 8 Apr 2024 19:20:22 -0600 Subject: [PATCH 10/14] ENH: volume restriction on objective --- .../SGH_Solver/src/FEA_Module_SGH.cpp | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp index e3b9fc6f3..d483b4e5e 100644 --- a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp +++ b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp @@ -1584,21 +1584,53 @@ void FEA_Module_SGH::sgh_solve() KE_loc_sum = 0.0; KE_sum = 0.0; // extensive KE - REDUCE_SUM_CLASS(node_gid, 0, nlocal_nodes, KE_loc_sum, { - double ke = 0; - for (size_t dim = 0; dim < num_dim; dim++) { - // midpoint integration approximation - ke += (node_velocities_interface(node_gid, dim) + previous_node_velocities_interface(node_gid, dim)) * (node_velocities_interface(node_gid, - dim) + previous_node_velocities_interface(node_gid, dim)) / 4; // 1/2 at end - } // end for - - if (num_dim == 2) { - KE_loc_sum += node_mass(node_gid) * node_coords(rk_level, node_gid, 1) * ke; - } - else{ - KE_loc_sum += node_mass(node_gid) * ke; - } - }, KE_sum); + if(simparam->optimization_options.optimization_objective_regions.size()){ + int nobj_volumes = simparam->optimization_options.optimization_objective_regions.size(); + REDUCE_SUM_CLASS(node_gid, 0, nlocal_nodes, KE_loc_sum, { + double ke = 0; + double current_node_coords[3]; + bool contained = false; + current_node_coords[0] = node_coords(rk_level, node_gid, 0); + current_node_coords[1] = node_coords(rk_level, node_gid, 1); + current_node_coords[2] = node_coords(rk_level, node_gid, 2); + for(int ivolume = 0; ivolume < nobj_volumes; ivolume++){ + if(simparam->optimization_options.optimization_objective_regions(ivolume).contains(current_node_coords)){ + contained = true; + } + } + if(contained){ + for (size_t dim = 0; dim < num_dim; dim++) { + // midpoint integration approximation + ke += (node_velocities_interface(node_gid, dim) + previous_node_velocities_interface(node_gid, dim)) * + (node_velocities_interface(node_gid, dim) + previous_node_velocities_interface(node_gid, dim)) / 4; // 1/2 at end + } // end for + } + + if (num_dim == 2) { + KE_loc_sum += node_mass(node_gid) * node_coords(rk_level, node_gid, 1) * ke; + } + else{ + KE_loc_sum += node_mass(node_gid) * ke; + } + }, KE_sum); + } + else{ + REDUCE_SUM_CLASS(node_gid, 0, nlocal_nodes, KE_loc_sum, { + double ke = 0; + for (size_t dim = 0; dim < num_dim; dim++) { + // midpoint integration approximation + ke += (node_velocities_interface(node_gid, dim) + previous_node_velocities_interface(node_gid, dim)) * + (node_velocities_interface(node_gid, dim) + previous_node_velocities_interface(node_gid, dim)) / 4; // 1/2 at end + } // end for + + if (num_dim == 2) { + KE_loc_sum += node_mass(node_gid) * node_coords(rk_level, node_gid, 1) * ke; + } + else{ + KE_loc_sum += node_mass(node_gid) * ke; + } + }, KE_sum); + } Kokkos::fence(); KE_sum = 0.5 * KE_sum; objective_accumulation += KE_sum * dt; From 0be8f8a39514ff5046ef44735c55dfe717c55a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAdrian-Diaz=E2=80=9D?= <“adriandiaz1117@gmail.com”> Date: Tue, 9 Apr 2024 12:31:04 -0600 Subject: [PATCH 11/14] ENH: objective subvolume gradient --- .../SGH_Solver/src/FEA_Module_SGH.cpp | 7 +- .../SGH_Solver/src/sgh_optimization.cpp | 284 +++++++++++++----- 2 files changed, 212 insertions(+), 79 deletions(-) diff --git a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp index d483b4e5e..93da7145e 100644 --- a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp +++ b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp @@ -1586,13 +1586,14 @@ void FEA_Module_SGH::sgh_solve() // extensive KE if(simparam->optimization_options.optimization_objective_regions.size()){ int nobj_volumes = simparam->optimization_options.optimization_objective_regions.size(); + const_vec_array all_initial_node_coords = all_initial_node_coords_distributed->getLocalView(Tpetra::Access::ReadOnly); REDUCE_SUM_CLASS(node_gid, 0, nlocal_nodes, KE_loc_sum, { double ke = 0; double current_node_coords[3]; bool contained = false; - current_node_coords[0] = node_coords(rk_level, node_gid, 0); - current_node_coords[1] = node_coords(rk_level, node_gid, 1); - current_node_coords[2] = node_coords(rk_level, node_gid, 2); + current_node_coords[0] = all_initial_node_coords(node_gid, 0); + current_node_coords[1] = all_initial_node_coords(node_gid, 1); + current_node_coords[2] = all_initial_node_coords(node_gid, 2); for(int ivolume = 0; ivolume < nobj_volumes; ivolume++){ if(simparam->optimization_options.optimization_objective_regions(ivolume).contains(current_node_coords)){ contained = true; diff --git a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/sgh_optimization.cpp b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/sgh_optimization.cpp index 3c1089c24..0a12c24d9 100644 --- a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/sgh_optimization.cpp +++ b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/sgh_optimization.cpp @@ -1,5 +1,5 @@ /********************************************************************************************** - 2020. Triad National Security, LLC. All rights reserved. + � 2020. Triad National Security, LLC. All rights reserved. This program was produced under U.S. Government contract 89233218CNA000001 for Los Alamos National Laboratory (LANL), which is operated by Triad National Security, LLC for the U.S. Department of Energy/National Nuclear Security Administration. All rights in the program are @@ -785,31 +785,72 @@ void FEA_Module_SGH::compute_topology_optimization_adjoint_full() vec_array psi_midpoint_adjoint_vector = psi_adjoint_vector_distributed->getLocalView(Tpetra::Access::ReadWrite); // half step update for RK2 scheme; EQUATION 1 - FOR_ALL_CLASS(node_gid, 0, nlocal_nodes, { - real_t rate_of_change; - real_t matrix_contribution; - size_t dof_id; - size_t elem_id; - for (int idim = 0; idim < num_dim; idim++) { - // EQUATION 1 - matrix_contribution = 0; - // compute resulting row of force velocity gradient matrix transpose right multiplied by adjoint vector - for (int idof = 0; idof < Gradient_Matrix_Strides(node_gid * num_dim + idim); idof++) { - dof_id = DOF_Graph_Matrix(node_gid * num_dim + idim, idof); - matrix_contribution += previous_adjoint_vector(dof_id / num_dim, dof_id % num_dim) * Force_Gradient_Velocities(node_gid * num_dim + idim, idof); + if(simparam->optimization_options.optimization_objective_regions.size()){ + int nobj_volumes = simparam->optimization_options.optimization_objective_regions.size(); + const_vec_array all_initial_node_coords = all_initial_node_coords_distributed->getLocalView(Tpetra::Access::ReadOnly); + FOR_ALL_CLASS(node_gid, 0, nlocal_nodes, { + real_t rate_of_change; + real_t matrix_contribution; + size_t dof_id; + size_t elem_id; + double current_node_coords[3]; + int contained = 0; + current_node_coords[0] = all_initial_node_coords(node_gid, 0); + current_node_coords[1] = all_initial_node_coords(node_gid, 1); + current_node_coords[2] = all_initial_node_coords(node_gid, 2); + for(int ivolume = 0; ivolume < nobj_volumes; ivolume++){ + if(simparam->optimization_options.optimization_objective_regions(ivolume).contains(current_node_coords)){ + contained = 1; + } } + for (int idim = 0; idim < num_dim; idim++) { + // EQUATION 1 + matrix_contribution = 0; + // compute resulting row of force velocity gradient matrix transpose right multiplied by adjoint vector + for (int idof = 0; idof < Gradient_Matrix_Strides(node_gid * num_dim + idim); idof++) { + dof_id = DOF_Graph_Matrix(node_gid * num_dim + idim, idof); + matrix_contribution += previous_adjoint_vector(dof_id / num_dim, dof_id % num_dim) * Force_Gradient_Velocities(node_gid * num_dim + idim, idof); + } - // compute resulting row of transpose of power gradient w.r.t velocity matrix right multiplied by psi adjoint vector - for (int ielem = 0; ielem < DOF_to_Elem_Matrix_Strides(node_gid * num_dim + idim); ielem++) { - elem_id = elems_in_node(node_gid, ielem); - matrix_contribution += psi_previous_adjoint_vector(elem_id, 0) * Power_Gradient_Velocities(node_gid * num_dim + idim, ielem); + // compute resulting row of transpose of power gradient w.r.t velocity matrix right multiplied by psi adjoint vector + for (int ielem = 0; ielem < DOF_to_Elem_Matrix_Strides(node_gid * num_dim + idim); ielem++) { + elem_id = elems_in_node(node_gid, ielem); + matrix_contribution += psi_previous_adjoint_vector(elem_id, 0) * Power_Gradient_Velocities(node_gid * num_dim + idim, ielem); + } + rate_of_change = contained*previous_velocity_vector(node_gid, idim) - + matrix_contribution / node_mass(node_gid) - + phi_previous_adjoint_vector(node_gid, idim) / node_mass(node_gid); + midpoint_adjoint_vector(node_gid, idim) = -rate_of_change * global_dt / 2 + previous_adjoint_vector(node_gid, idim); } - rate_of_change = previous_velocity_vector(node_gid, idim) - - matrix_contribution / node_mass(node_gid) - - phi_previous_adjoint_vector(node_gid, idim) / node_mass(node_gid); - midpoint_adjoint_vector(node_gid, idim) = -rate_of_change * global_dt / 2 + previous_adjoint_vector(node_gid, idim); - } - }); // end parallel for + }); // end parallel for + } + else{ + FOR_ALL_CLASS(node_gid, 0, nlocal_nodes, { + real_t rate_of_change; + real_t matrix_contribution; + size_t dof_id; + size_t elem_id; + for (int idim = 0; idim < num_dim; idim++) { + // EQUATION 1 + matrix_contribution = 0; + // compute resulting row of force velocity gradient matrix transpose right multiplied by adjoint vector + for (int idof = 0; idof < Gradient_Matrix_Strides(node_gid * num_dim + idim); idof++) { + dof_id = DOF_Graph_Matrix(node_gid * num_dim + idim, idof); + matrix_contribution += previous_adjoint_vector(dof_id / num_dim, dof_id % num_dim) * Force_Gradient_Velocities(node_gid * num_dim + idim, idof); + } + + // compute resulting row of transpose of power gradient w.r.t velocity matrix right multiplied by psi adjoint vector + for (int ielem = 0; ielem < DOF_to_Elem_Matrix_Strides(node_gid * num_dim + idim); ielem++) { + elem_id = elems_in_node(node_gid, ielem); + matrix_contribution += psi_previous_adjoint_vector(elem_id, 0) * Power_Gradient_Velocities(node_gid * num_dim + idim, ielem); + } + rate_of_change = previous_velocity_vector(node_gid, idim) - + matrix_contribution / node_mass(node_gid) - + phi_previous_adjoint_vector(node_gid, idim) / node_mass(node_gid); + midpoint_adjoint_vector(node_gid, idim) = -rate_of_change * global_dt / 2 + previous_adjoint_vector(node_gid, idim); + } + }); // end parallel for + } Kokkos::fence(); // half step update for RK2 scheme; EQUATION 2 @@ -1049,33 +1090,76 @@ void FEA_Module_SGH::compute_topology_optimization_adjoint_full() corner_force); // full step update with midpoint gradient for RK2 scheme; EQUATION 1 - FOR_ALL_CLASS(node_gid, 0, nlocal_nodes, { - real_t rate_of_change; - real_t matrix_contribution; - size_t dof_id; - size_t elem_id; - for (int idim = 0; idim < num_dim; idim++) { - // EQUATION 1 - matrix_contribution = 0; - // compute resulting row of force velocity gradient matrix transpose right multiplied by adjoint vector - - for (int idof = 0; idof < Gradient_Matrix_Strides(node_gid * num_dim + idim); idof++) { - dof_id = DOF_Graph_Matrix(node_gid * num_dim + idim, idof); - matrix_contribution += midpoint_adjoint_vector(dof_id / num_dim, dof_id % num_dim) * Force_Gradient_Velocities(node_gid * num_dim + idim, idof); + if(simparam->optimization_options.optimization_objective_regions.size()){ + int nobj_volumes = simparam->optimization_options.optimization_objective_regions.size(); + const_vec_array all_initial_node_coords = all_initial_node_coords_distributed->getLocalView(Tpetra::Access::ReadOnly); + FOR_ALL_CLASS(node_gid, 0, nlocal_nodes, { + real_t rate_of_change; + real_t matrix_contribution; + size_t dof_id; + size_t elem_id; + double current_node_coords[3]; + int contained = 0; + current_node_coords[0] = all_initial_node_coords(node_gid, 0); + current_node_coords[1] = all_initial_node_coords(node_gid, 1); + current_node_coords[2] = all_initial_node_coords(node_gid, 2); + for(int ivolume = 0; ivolume < nobj_volumes; ivolume++){ + if(simparam->optimization_options.optimization_objective_regions(ivolume).contains(current_node_coords)){ + contained = 1; + } } + for (int idim = 0; idim < num_dim; idim++) { + // EQUATION 1 + matrix_contribution = 0; + // compute resulting row of force velocity gradient matrix transpose right multiplied by adjoint vector - // compute resulting row of transpose of power gradient w.r.t velocity matrix right multiplied by psi adjoint vector - for (int ielem = 0; ielem < DOF_to_Elem_Matrix_Strides(node_gid * num_dim + idim); ielem++) { - elem_id = elems_in_node(node_gid, ielem); - matrix_contribution += psi_midpoint_adjoint_vector(elem_id, 0) * Power_Gradient_Velocities(node_gid * num_dim + idim, ielem); + for (int idof = 0; idof < Gradient_Matrix_Strides(node_gid * num_dim + idim); idof++) { + dof_id = DOF_Graph_Matrix(node_gid * num_dim + idim, idof); + matrix_contribution += midpoint_adjoint_vector(dof_id / num_dim, dof_id % num_dim) * Force_Gradient_Velocities(node_gid * num_dim + idim, idof); + } + + // compute resulting row of transpose of power gradient w.r.t velocity matrix right multiplied by psi adjoint vector + for (int ielem = 0; ielem < DOF_to_Elem_Matrix_Strides(node_gid * num_dim + idim); ielem++) { + elem_id = elems_in_node(node_gid, ielem); + matrix_contribution += psi_midpoint_adjoint_vector(elem_id, 0) * Power_Gradient_Velocities(node_gid * num_dim + idim, ielem); + } + + rate_of_change = 0.5*contained*(previous_velocity_vector(node_gid, idim) + current_velocity_vector(node_gid, idim)) - + matrix_contribution / node_mass(node_gid) - + phi_midpoint_adjoint_vector(node_gid, idim) / node_mass(node_gid); + current_adjoint_vector(node_gid, idim) = -rate_of_change * global_dt + previous_adjoint_vector(node_gid, idim); } + }); // end parallel for + } + else{ + FOR_ALL_CLASS(node_gid, 0, nlocal_nodes, { + real_t rate_of_change; + real_t matrix_contribution; + size_t dof_id; + size_t elem_id; + for (int idim = 0; idim < num_dim; idim++) { + // EQUATION 1 + matrix_contribution = 0; + // compute resulting row of force velocity gradient matrix transpose right multiplied by adjoint vector - rate_of_change = (previous_velocity_vector(node_gid, idim) + current_velocity_vector(node_gid, idim)) / 2 - - matrix_contribution / node_mass(node_gid) - - phi_midpoint_adjoint_vector(node_gid, idim) / node_mass(node_gid); - current_adjoint_vector(node_gid, idim) = -rate_of_change * global_dt + previous_adjoint_vector(node_gid, idim); - } - }); // end parallel for + for (int idof = 0; idof < Gradient_Matrix_Strides(node_gid * num_dim + idim); idof++) { + dof_id = DOF_Graph_Matrix(node_gid * num_dim + idim, idof); + matrix_contribution += midpoint_adjoint_vector(dof_id / num_dim, dof_id % num_dim) * Force_Gradient_Velocities(node_gid * num_dim + idim, idof); + } + + // compute resulting row of transpose of power gradient w.r.t velocity matrix right multiplied by psi adjoint vector + for (int ielem = 0; ielem < DOF_to_Elem_Matrix_Strides(node_gid * num_dim + idim); ielem++) { + elem_id = elems_in_node(node_gid, ielem); + matrix_contribution += psi_midpoint_adjoint_vector(elem_id, 0) * Power_Gradient_Velocities(node_gid * num_dim + idim, ielem); + } + + rate_of_change = 0.5*(previous_velocity_vector(node_gid, idim) + current_velocity_vector(node_gid, idim)) - + matrix_contribution / node_mass(node_gid) - + phi_midpoint_adjoint_vector(node_gid, idim) / node_mass(node_gid); + current_adjoint_vector(node_gid, idim) = -rate_of_change * global_dt + previous_adjoint_vector(node_gid, idim); + } + }); // end parallel for + } Kokkos::fence(); // full step update with midpoint gradient for RK2 scheme; EQUATION 2 @@ -1204,41 +1288,89 @@ void FEA_Module_SGH::compute_topology_optimization_gradient_full(Teuchos::RCPgetLocalView(Tpetra::Access::ReadOnly); const_vec_array next_velocity_vector = (*forward_solve_velocity_data)[cycle + 1]->getLocalView(Tpetra::Access::ReadOnly); + if(simparam->optimization_options.optimization_objective_regions.size()){ + int nobj_volumes = simparam->optimization_options.optimization_objective_regions.size(); + const_vec_array all_initial_node_coords = all_initial_node_coords_distributed->getLocalView(Tpetra::Access::ReadOnly); + FOR_ALL_CLASS(elem_id, 0, rnum_elem, { + size_t node_id; + size_t corner_id; + real_t inner_product; + // std::cout << elem_mass(elem_id) <optimization_options.optimization_objective_regions(ivolume).contains(current_node_coords)){ + contained = true; + } + } + if(contained){ + for (int idim = 0; idim < num_dim; idim++) { + inner_product += elem_mass(elem_id) * current_element_velocities(ifill, idim) * current_element_velocities(ifill, idim); + } + } } - } - inner_product = 0; - for (int ifill = 0; ifill < num_nodes_in_elem; ifill++) { - node_id = nodes_in_elem(elem_id, ifill); - for (int idim = 0; idim < num_dim; idim++) { - inner_product += elem_mass(elem_id) * current_element_velocities(ifill, idim) * current_element_velocities(ifill, idim); + for (int inode = 0; inode < num_nodes_in_elem; inode++) { + // compute gradient of local element contribution to v^t*M*v product + corner_id = elem_id * num_nodes_in_elem + inode; + // division by design ratio recovers nominal element mass used in the gradient operator + corner_value_storage(corner_id) = inner_product * global_dt / relative_element_densities(elem_id); + } + }); // end parallel for + Kokkos::fence(); + } + else{ + FOR_ALL_CLASS(elem_id, 0, rnum_elem, { + size_t node_id; + size_t corner_id; + real_t inner_product; + // std::cout << elem_mass(elem_id) < Date: Sat, 13 Apr 2024 10:28:54 -0600 Subject: [PATCH 12/14] BUG: device side undefined pointer --- .../SGH_Solver/src/FEA_Module_SGH.cpp | 12 ++++--- .../SGH_Solver/src/setup_sgh.cpp | 36 ++++++++++--------- .../SGH_Solver/src/sgh_optimization.cpp | 7 ++-- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp index 93da7145e..71b6de1bb 100644 --- a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp +++ b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/FEA_Module_SGH.cpp @@ -900,6 +900,8 @@ void FEA_Module_SGH::sgh_solve() std::vector> FEA_Module_My_TO_Modules = simparam->FEA_Module_My_TO_Modules; problem = Explicit_Solver_Pointer_->problem; // Pointer to ROL optimization problem object ROL::Ptr> obj_pointer; + bool topology_optimization_on = simparam->topology_optimization_on; + bool shape_optimization_on = simparam->shape_optimization_on; // reset time accumulating objective and constraints /* @@ -912,7 +914,7 @@ void FEA_Module_SGH::sgh_solve() } */ // simple setup to just request KE for now; above loop to be expanded and used later for scanning modules - if (simparam->topology_optimization_on) { + if (topology_optimization_on) { obj_pointer = problem->getObjective(); KineticEnergyMinimize_TopOpt& kinetic_energy_minimize_function = dynamic_cast(*obj_pointer); kinetic_energy_minimize_function.objective_accumulation = 0; @@ -939,7 +941,7 @@ void FEA_Module_SGH::sgh_solve() } } - if (simparam->topology_optimization_on) { + if (topology_optimization_on) { nTO_modules = simparam->TO_Module_List.size(); } @@ -1032,7 +1034,7 @@ void FEA_Module_SGH::sgh_solve() auto time_1 = std::chrono::high_resolution_clock::now(); // save initial data - if (simparam->topology_optimization_on || simparam->shape_optimization_on) { + if (topology_optimization_on || shape_optimization_on) { time_data[0] = 0; // assign current velocity data to multivector // view scope @@ -1479,7 +1481,7 @@ void FEA_Module_SGH::sgh_solve() // increment the time Explicit_Solver_Pointer_->time_value = simparam->dynamic_options.time_value = time_value += dt; - if (simparam->topology_optimization_on || simparam->shape_optimization_on) { + if (topology_optimization_on || shape_optimization_on) { if (cycle >= max_time_steps) { max_time_steps = cycle + 1; } @@ -1687,7 +1689,7 @@ void FEA_Module_SGH::sgh_solve() last_time_step = cycle; // simple setup to just calculate KE minimize objective for now - if (simparam->topology_optimization_on) { + if (topology_optimization_on) { KineticEnergyMinimize_TopOpt& kinetic_energy_minimize_function = dynamic_cast(*obj_pointer); // collect local objective values diff --git a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/setup_sgh.cpp b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/setup_sgh.cpp index bb115da40..ee54c231a 100644 --- a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/setup_sgh.cpp +++ b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/setup_sgh.cpp @@ -49,13 +49,15 @@ ///////////////////////////////////////////////////////////////////////////// void FEA_Module_SGH::setup() { - const size_t rk_level = simparam->dynamic_options.rk_num_bins - 1; - const size_t num_fills = simparam->regions.size(); - const size_t rk_num_bins = simparam->dynamic_options.rk_num_bins; - const size_t num_bcs = module_params->boundary_conditions.size(); - const size_t num_materials = simparam->materials.size(); - const int num_dim = simparam->num_dims; - const size_t num_lcs = module_params->loading.size(); + const size_t rk_level = simparam->dynamic_options.rk_num_bins - 1; + const size_t num_fills = simparam->regions.size(); + const size_t rk_num_bins = simparam->dynamic_options.rk_num_bins; + const size_t num_bcs = module_params->boundary_conditions.size(); + const size_t num_materials = simparam->materials.size(); + const int num_dim = simparam->num_dims; + const size_t num_lcs = module_params->loading.size(); + bool topology_optimization_on = simparam->topology_optimization_on; + bool shape_optimization_on = simparam->shape_optimization_on; if (num_lcs) { have_loading_conditions = true; } @@ -121,7 +123,7 @@ void FEA_Module_SGH::setup() elem_strength = DCArrayKokkos(num_elems); // optimization flags - if (simparam->topology_optimization_on) { + if (topology_optimization_on) { elem_extensive_initial_energy_condition = DCArrayKokkos(num_elems); } @@ -245,7 +247,7 @@ void FEA_Module_SGH::setup() // --- apply the fill instructions over each of the Elements---// // initialize if topology optimization is used - if (simparam->topology_optimization_on) { + if (topology_optimization_on) { // compute element averaged density ratios corresponding to nodal density design variables CArray current_element_nodal_densities = CArray(num_nodes_in_elem); { // view scope @@ -297,7 +299,7 @@ void FEA_Module_SGH::setup() // paint the material state on the element if (fill_this) { // density - if (simparam->topology_optimization_on) { + if (topology_optimization_on) { elem_den(elem_gid) = mat_fill(f_id).den * relative_element_densities(elem_gid); } else{ @@ -310,11 +312,11 @@ void FEA_Module_SGH::setup() // specific internal energy elem_sie(rk_level, elem_gid) = mat_fill(f_id).sie; - if (simparam->topology_optimization_on && mat_fill(f_id).extensive_energy_setting) { + if (topology_optimization_on && mat_fill(f_id).extensive_energy_setting) { elem_sie(rk_level, elem_gid) = elem_sie(rk_level, elem_gid) / relative_element_densities(elem_gid); elem_extensive_initial_energy_condition(elem_gid) = true; } - else if (simparam->topology_optimization_on && !mat_fill(f_id).extensive_energy_setting) { + else if (topology_optimization_on && !mat_fill(f_id).extensive_energy_setting) { elem_extensive_initial_energy_condition(elem_gid) = false; } @@ -521,13 +523,13 @@ void FEA_Module_SGH::setup() // current interface has differing mass arrays; this equates them until we unify memory // view scope - if (simparam->topology_optimization_on || simparam->shape_optimization_on || simparam->num_dims == 2) { + if (topology_optimization_on || shape_optimization_on || simparam->num_dims == 2) { { vec_array node_mass_interface = node_masses_distributed->getLocalView(Tpetra::Access::ReadWrite); FOR_ALL_CLASS(node_gid, 0, nlocal_nodes, { node_mass_interface(node_gid, 0) = node_mass(node_gid); - }); // end parallel for + }); // end parallel for } // end view scope Kokkos::fence(); // communicate ghost densities @@ -540,13 +542,13 @@ void FEA_Module_SGH::setup() FOR_ALL_CLASS(node_gid, nlocal_nodes, nall_nodes, { node_mass(node_gid) = ghost_node_mass_interface(node_gid - nlocal_nodes, 0); - }); // end parallel for + }); // end parallel for } // end view scope Kokkos::fence(); } // endif // initialize if topology optimization is used - if (simparam->topology_optimization_on || simparam->shape_optimization_on) { + if (topology_optimization_on || shape_optimization_on) { init_assembly(); // assemble_matrix(); } @@ -559,7 +561,7 @@ void FEA_Module_SGH::setup() elem_pres.update_host(); elem_sspd.update_host(); - if (simparam->topology_optimization_on) { + if (topology_optimization_on) { elem_extensive_initial_energy_condition.update_host(); } diff --git a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/sgh_optimization.cpp b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/sgh_optimization.cpp index 0a12c24d9..2d4520b8b 100644 --- a/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/sgh_optimization.cpp +++ b/src/Parallel-Solvers/Parallel-Explicit/SGH_Solver/src/sgh_optimization.cpp @@ -109,6 +109,9 @@ void FEA_Module_SGH::update_forward_solve(Teuchos::RCP zp) std::vector> FEA_Module_My_TO_Modules = simparam->FEA_Module_My_TO_Modules; problem = Explicit_Solver_Pointer_->problem; // Pointer to ROL optimization problem object ROL::Ptr> obj_pointer; + + bool topology_optimization_on = simparam->topology_optimization_on; + bool shape_optimization_on = simparam->shape_optimization_on; // compute element averaged density ratios corresponding to nodal density design variables { // view scope @@ -265,7 +268,7 @@ void FEA_Module_SGH::update_forward_solve(Teuchos::RCP zp) // specific internal energy elem_sie(rk_level, elem_gid) = mat_fill(f_id).sie; - if (simparam->topology_optimization_on && mat_fill(f_id).extensive_energy_setting) { + if (topology_optimization_on && mat_fill(f_id).extensive_energy_setting) { elem_sie(rk_level, elem_gid) = elem_sie(rk_level, elem_gid) / relative_element_densities(elem_gid); } @@ -496,7 +499,7 @@ void FEA_Module_SGH::update_forward_solve(Teuchos::RCP zp) Kokkos::fence(); // update stiffness matrix - if (simparam->topology_optimization_on || simparam->shape_optimization_on) { + if (topology_optimization_on || shape_optimization_on) { // assemble_matrix(); } From 3d0510da0521f9787db679785b4c0d5f06263ea8 Mon Sep 17 00:00:00 2001 From: Adrian-Diaz Date: Sun, 21 Apr 2024 23:26:46 -0600 Subject: [PATCH 13/14] BUG: cmake build for mesh builder and voxelizer --- src/Mesh-Builder/CMakeLists.txt | 18 ++++++++++++++++++ src/Voxelizer/CMakeLists.txt | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/Mesh-Builder/CMakeLists.txt b/src/Mesh-Builder/CMakeLists.txt index 8f5ba7538..8ac4ec50e 100644 --- a/src/Mesh-Builder/CMakeLists.txt +++ b/src/Mesh-Builder/CMakeLists.txt @@ -4,6 +4,24 @@ project(mesh-builder) SET(CMAKE_CXX_STANDARD 17) +find_package(MPI REQUIRED) + +# Assume if the CXX compiler exists, the rest do too. +if (EXISTS ${Trilinos_CXX_COMPILER}) + set(CMAKE_CXX_COMPILER ${Trilinos_CXX_COMPILER}) + set(CMAKE_C_COMPILER ${Trilinos_C_COMPILER}) + set(CMAKE_Fortran_COMPILER ${Trilinos_Fortran_COMPILER}) +endif() +if(NOT DISTRIBUTION) + # Make sure to use same compilers and flags as Trilinos + set(CMAKE_CXX_FLAGS "${Trilinos_CXX_COMPILER_FLAGS} ${CMAKE_CXX_FLAGS}") + set(CMAKE_C_FLAGS "${Trilinos_C_COMPILER_FLAGS} ${CMAKE_C_FLAGS}") + set(CMAKE_Fortran_FLAGS "${Trilinos_Fortran_COMPILER_FLAGS} ${CMAKE_Fortran_FLAGS}") +endif() + +include_directories(${Trilinos_INCLUDE_DIRS} ${Trilinos_TPL_INCLUDE_DIRS}) +link_directories(${Trilinos_LIBRARY_DIRS} ${Trilinos_TPL_LIBRARY_DIRS}) + add_library(mesh_builder src/MeshBuilder.cpp src/EnsightIO.cpp src/VtkIO.cpp) target_link_libraries(mesh_builder PUBLIC yaml_serializable Elements) target_include_directories(mesh_builder PUBLIC include) diff --git a/src/Voxelizer/CMakeLists.txt b/src/Voxelizer/CMakeLists.txt index 9f54db52f..0528ecde2 100644 --- a/src/Voxelizer/CMakeLists.txt +++ b/src/Voxelizer/CMakeLists.txt @@ -5,11 +5,28 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD 17) find_package(Elements REQUIRED) +find_package(MPI REQUIRED) + +# Assume if the CXX compiler exists, the rest do too. +if (EXISTS ${Trilinos_CXX_COMPILER}) + set(CMAKE_CXX_COMPILER ${Trilinos_CXX_COMPILER}) + set(CMAKE_C_COMPILER ${Trilinos_C_COMPILER}) + set(CMAKE_Fortran_COMPILER ${Trilinos_Fortran_COMPILER}) +endif() +if(NOT DISTRIBUTION) + # Make sure to use same compilers and flags as Trilinos + set(CMAKE_CXX_FLAGS "${Trilinos_CXX_COMPILER_FLAGS} ${CMAKE_CXX_FLAGS}") + set(CMAKE_C_FLAGS "${Trilinos_C_COMPILER_FLAGS} ${CMAKE_C_FLAGS}") + set(CMAKE_Fortran_FLAGS "${Trilinos_Fortran_COMPILER_FLAGS} ${CMAKE_Fortran_FLAGS}") +endif() add_library(voxelizer src/stl-to-voxelvtk.cpp) target_include_directories(voxelizer PUBLIC include) target_link_libraries(voxelizer PUBLIC Elements) +include_directories(${Trilinos_INCLUDE_DIRS} ${Trilinos_TPL_INCLUDE_DIRS}) +link_directories(${Trilinos_LIBRARY_DIRS} ${Trilinos_TPL_LIBRARY_DIRS}) + add_executable(fierro-voxelizer src/main.cpp) target_link_libraries(fierro-voxelizer voxelizer) target_compile_options(fierro-voxelizer PRIVATE "-fopenmp") From 0ca937c798d5cce78dadd4ad5fd478328b6e9fd8 Mon Sep 17 00:00:00 2001 From: Jacob Moore Date: Fri, 12 Apr 2024 09:16:20 -0500 Subject: [PATCH 14/14] ENH: Removing CLI --- src/CLI/CMakeLists.txt | 14 - src/CLI/README.md | 168 -- src/CLI/include/FierroParallelBackends.hpp | 80 - src/CLI/include/MeshBuilderBackend.hpp | 83 - src/CLI/include/VoxelizerBackend.hpp | 84 - src/CLI/include/argparse/argparse.hpp | 1698 -------------------- src/CLI/include/argument_exception.hpp | 15 - src/CLI/include/backend.hpp | 73 - src/CLI/src/main.cpp | 100 -- src/CMakeLists.txt | 2 +- 10 files changed, 1 insertion(+), 2316 deletions(-) delete mode 100644 src/CLI/CMakeLists.txt delete mode 100644 src/CLI/README.md delete mode 100644 src/CLI/include/FierroParallelBackends.hpp delete mode 100644 src/CLI/include/MeshBuilderBackend.hpp delete mode 100644 src/CLI/include/VoxelizerBackend.hpp delete mode 100644 src/CLI/include/argparse/argparse.hpp delete mode 100644 src/CLI/include/argument_exception.hpp delete mode 100644 src/CLI/include/backend.hpp delete mode 100644 src/CLI/src/main.cpp diff --git a/src/CLI/CMakeLists.txt b/src/CLI/CMakeLists.txt deleted file mode 100644 index 70e0fefa3..000000000 --- a/src/CLI/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -cmake_minimum_required(VERSION 3.17) -project(fierro-cli) - -set(CMAKE_CXX_STANDARD 17) -add_executable(fierro src/main.cpp) -target_include_directories(fierro PUBLIC include) - -# HIPCC is a driver for Clang, and Clang wants you to -# link against lstdc++fs for version 15. -if (NOT APPLE) - target_link_options(fierro PRIVATE -lstdc++fs) -endif() - -install(TARGETS fierro) diff --git a/src/CLI/README.md b/src/CLI/README.md deleted file mode 100644 index 9fb134b7e..000000000 --- a/src/CLI/README.md +++ /dev/null @@ -1,168 +0,0 @@ -# Fiero Command Line Interface -This CLI package is a Fierro command line interface that is intended to act as a consolidated front-end for our Fierro backend tools. It provides a consistent interface and framework for exposing tools to the user. - -## Front-End -The front end here is built off of [argparse](https://github.com/p-ranav/argparse), which implements a language agnostic standard for simple command line interface design. It defines a syntax for programs from the command line with suitable helptext/defaults/post-processing. - -The Fierro-CLI implements two things: -1. Output directory -- A directory to run the program in. Outputs of the program will by default be put here if some other backend specific option is not specified. -2. Subcommands -- a list of argparse "subcommands" that each represent an available backend tool. - -Example: -``` -$ fierro -h - -Usage: fierro [--help] [--version] [--output VAR] {mesh-builder,parallel-explicit,parallel-implicit,voxelizer} - -Optional arguments: - -h, --help shows help message and exits - -v, --version prints version information and exits - -o, --output output directory for the program [default: "."] - -Subcommands: - mesh-builder Build rectangular or cylindrical mesh in 2 or 3 dimensions. Useful for setting up test problems. - parallel-explicit Use an explicit solution scheme to step the system. - parallel-implicit Use an implicit solution scheme to step the system. - voxelizer Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. Note: Only supports binary STL mesh inputs -``` - -## Backends -This Fierro-CLI package is designed to operate decoupled from backends. Each registered backend is an executable that may or may not be present in the system. - -### Registering Backends -To add a new backend, you only need to do a couple of steps. -#### 1. Implementing NewBackend.hpp -You should start by copying one of the existing backend.hpp files and implementing your new backend. We will use `VoxelizerBackend.hpp` as an example. - -You should derive from `FierroBackend` and tell the base class which executable name to look for -```c++ -#include "backend.hpp" -struct VoxelizerBackend: public FierroBackend { - VoxelizerBackend() : FierroBackend("fierro-voxelizer") { - ... - } - ... -}; -``` - -Then, in the constructor, setup your argparse command - -```c++ - -struct VoxelizerBackend: public FierroBackend { - VoxelizerBackend() : FierroBackend("fierro-voxelizer") { - - this->command = std::shared_ptr( - new argparse::ArgumentParser("voxelizer") // <--- This is the name that shows up under "Subcommands:" in the CLI - ); - - // Here you can add your arguments with their helptext. - this->command->add_argument("input-file") - .help("The binary STL file to voxelize.") - .action(absolute_fp); // <-- You can also add a post-processing command. In this case we convert the input filepath to an absolute one. - this->command->add_argument("output-name") - .help("The name of the VTK structured mesh output file."); - this->command->add_argument("x") - .help("The number of voxels along the X direction (int)."); - this->command->add_argument("y") - .help("The number of voxels along the Y direction (int)."); - this->command->add_argument("z") - .help("The number of voxels along the Z direction (int)."); - this->command->add_argument("use-index-space") - .help("Wether or not to output the VTK in index-space coordinates."); - - // Add a description for your subcommand - this->command->add_description( - "Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. " - "The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. " - "Note: Only supports binary STL mesh inputs." - ); - } - ... -}; -``` - -Lastly you tell the CLI framework how to actually invoke the backend command. This is mostly just building a system command string and invoking it, but I suppose you could do whatever you want. -```c++ - -struct VoxelizerBackend: public FierroBackend { - ... - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string sys_command; - sys_command = this->exec_path.value().string() - + " " + this->command->get("input-file") - + " " + this->command->get("output-name") - + " " + this->command->get("x") - + " " + this->command->get("y") - + " " + this->command->get("z") - + " " + this->command->get("use-index-space"); - - return system(sys_command.c_str()); - } -}; -``` - -You probably also want to add some validation to your input arguments. The voxelizer does this with a serparate function: - -```c++ -struct VoxelizerBackend: public FierroBackend { - ... - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - - // Here we just check if the input file is an actual file. - if (!file_exists(parser->get("input-file"))) { - msg = "Unable to find input file: " + parser->get("input-file"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return {}; - } - ... -}; -``` - -Now you have a well defined backend. - - -#### 2. Plug it into the CLI framework -Head over to main.cpp and add your backend to the list of backends: -```c++ -std::vector> BACKENDS { - std::shared_ptr(), - std::shared_ptr(), - std::shared_ptr(), - std::shared_ptr(), - //std::shared_ptr() <-- Add yours here -}; - -``` - -Now you have everything set up, and it will automatically be picked up if its compiled and in the system. \ No newline at end of file diff --git a/src/CLI/include/FierroParallelBackends.hpp b/src/CLI/include/FierroParallelBackends.hpp deleted file mode 100644 index 0926f7b0e..000000000 --- a/src/CLI/include/FierroParallelBackends.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef FIERRO_PARALLEL_BACKENDS -#define FIERRO_PARALLEL_BACKENDS -#include "backend.hpp" -#include "argparse/argparse.hpp" -#include -#include - -struct ParallelBackend: public FierroBackend { - ParallelBackend(std::string name) - : FierroBackend(name) { } - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - - if (!file_exists(parser->get("config"))) { - msg = "Unable to find configuration: " + parser->get("config"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return std::nullopt; - } - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string input_file = this->command->get("config"); - std::string sys_command = this->exec_path.value().string() + " " + input_file; - - return system(sys_command.c_str()); - } - - void add_common_options() { - this->command->add_argument("config") - .help("The `.yaml` configuration to run fierro with.`") - .action(absolute_fp); - } -}; - -struct ParallelExplicit: public ParallelBackend { - ParallelExplicit() : ParallelBackend("fierro-parallel-explicit") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("parallel-explicit") - ); - this->add_common_options(); - this->command->add_description("Use an explicit solution scheme to step the system."); - } -}; - -struct ParallelImplicit: public ParallelBackend { - ParallelImplicit() : ParallelBackend("fierro-parallel-implicit") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("parallel-implicit") - ); - this->add_common_options(); - this->command->add_description("Use an implicit solution scheme to step the system."); - } -}; -#endif \ No newline at end of file diff --git a/src/CLI/include/MeshBuilderBackend.hpp b/src/CLI/include/MeshBuilderBackend.hpp deleted file mode 100644 index 22f2e217d..000000000 --- a/src/CLI/include/MeshBuilderBackend.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef MESH_BUILDER_BACKENDS_H -#define MESH_BUILDER_BACKENDS_H - -#include "backend.hpp" -#include "argparse/argparse.hpp" -#include -#include - -struct MeshBuilderBackend: public FierroBackend { - MeshBuilderBackend() : FierroBackend("fierro-mesh-builder") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("mesh-builder") - ); - - this->command->add_argument("-c", "config") - .help("The `.yaml` configuration to run fierro with.`") - .action(absolute_fp); - this->command->add_argument("--box-help") - .help("Set this flag to print out an example box mesh configuration file.") - .default_value(false) - .implicit_value(true); - this->command->add_argument("--cylinder-help") - .help("Set this flag to print out an example cylinder mesh configuration file.") - .default_value(false) - .implicit_value(true);; - this->command->add_description("Build rectangular or cylindrical mesh in 2 or 3 dimensions. Useful for setting up test problems."); - } - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - if (this->command->is_used("box-help") || this->command->is_used("cylinder-help")) - return {}; - - if (!parser->is_used("config")) - msg = "A value for config is required"; - else if (!file_exists(parser->get("config"))) { - msg = "Unable to find configuration: " + parser->get("config"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return {}; - } - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string sys_command; - if (this->command->get("box-help")) - sys_command = this->exec_path.value().string() + " Box"; - else if (this->command->get("cylinder-help")) - sys_command = this->exec_path.value().string() + " Cylinder"; - else { - std::string input_file = this->command->get("config"); - sys_command = this->exec_path.value().string() + " " + input_file; - } - - return system(sys_command.c_str()); - } -}; - -#endif \ No newline at end of file diff --git a/src/CLI/include/VoxelizerBackend.hpp b/src/CLI/include/VoxelizerBackend.hpp deleted file mode 100644 index 63ca7c769..000000000 --- a/src/CLI/include/VoxelizerBackend.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef VOXELIZER_BACKENDS_H -#define VOXELIZER_BACKENDS_H - -#include "backend.hpp" -#include "argparse/argparse.hpp" -#include -#include - -struct VoxelizerBackend: public FierroBackend { - VoxelizerBackend() : FierroBackend("fierro-voxelizer") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("voxelizer") - ); - - this->command->add_argument("input-file") - .help("The binary STL file to voxelize.") - .action(absolute_fp); - this->command->add_argument("output-name") - .help("The name of the VTK structured mesh output file."); - this->command->add_argument("x") - .help("The number of voxels along the X direction (int)."); - this->command->add_argument("y") - .help("The number of voxels along the Y direction (int)."); - this->command->add_argument("z") - .help("The number of voxels along the Z direction (int)."); - this->command->add_description( - "Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. " - "The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. " - "Note: Only supports binary STL mesh inputs." - ); - this->command->add_argument("use-index-space") - .help("Wether or not to output the VTK in index-space coordinates."); - } - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - - if (!file_exists(parser->get("input-file"))) { - msg = "Unable to find input file: " + parser->get("input-file"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return {}; - } - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string sys_command; - sys_command = this->exec_path.value().string() - + " " + this->command->get("input-file") - + " " + this->command->get("output-name") - + " " + this->command->get("x") - + " " + this->command->get("y") - + " " + this->command->get("z") - + " " + this->command->get("use-index-space"); - - return system(sys_command.c_str()); - } -}; - -#endif \ No newline at end of file diff --git a/src/CLI/include/argparse/argparse.hpp b/src/CLI/include/argparse/argparse.hpp deleted file mode 100644 index 633f7820d..000000000 --- a/src/CLI/include/argparse/argparse.hpp +++ /dev/null @@ -1,1698 +0,0 @@ -/* - __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ - / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ -| (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse - \__,_|_| \__, | .__/ \__,_|_| |___/\___| - |___/|_| - -Licensed under the MIT License . -SPDX-License-Identifier: MIT -Copyright (c) 2019-2022 Pranav Srinivas Kumar -and other contributors. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace argparse { - -namespace details { // namespace for helper methods - -template -struct HasContainerTraits : std::false_type {}; - -template <> struct HasContainerTraits : std::false_type {}; - -template <> struct HasContainerTraits : std::false_type {}; - -template -struct HasContainerTraits< - T, std::void_t().begin()), - decltype(std::declval().end()), - decltype(std::declval().size())>> : std::true_type {}; - -template -static constexpr bool IsContainer = HasContainerTraits::value; - -template -struct HasStreamableTraits : std::false_type {}; - -template -struct HasStreamableTraits< - T, - std::void_t() << std::declval())>> - : std::true_type {}; - -template -static constexpr bool IsStreamable = HasStreamableTraits::value; - -constexpr std::size_t repr_max_container_size = 5; - -template std::string repr(T const &val) { - if constexpr (std::is_same_v) { - return val ? "true" : "false"; - } else if constexpr (std::is_convertible_v) { - return '"' + std::string{std::string_view{val}} + '"'; - } else if constexpr (IsContainer) { - std::stringstream out; - out << "{"; - const auto size = val.size(); - if (size > 1) { - out << repr(*val.begin()); - std::for_each( - std::next(val.begin()), - std::next( - val.begin(), - static_cast( - std::min(size, repr_max_container_size) - 1)), - [&out](const auto &v) { out << " " << repr(v); }); - if (size <= repr_max_container_size) { - out << " "; - } else { - out << "..."; - } - } - if (size > 0) { - out << repr(*std::prev(val.end())); - } - out << "}"; - return out.str(); - } else if constexpr (IsStreamable) { - std::stringstream out; - out << val; - return out.str(); - } else { - return ""; - } -} - -namespace { - -template constexpr bool standard_signed_integer = false; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; - -template constexpr bool standard_unsigned_integer = false; -template <> constexpr bool standard_unsigned_integer = true; -template <> constexpr bool standard_unsigned_integer = true; -template <> constexpr bool standard_unsigned_integer = true; -template <> constexpr bool standard_unsigned_integer = true; -template <> -constexpr bool standard_unsigned_integer = true; - -} // namespace - -constexpr int radix_8 = 8; -constexpr int radix_10 = 10; -constexpr int radix_16 = 16; - -template -constexpr bool standard_integer = - standard_signed_integer || standard_unsigned_integer; - -template -constexpr decltype(auto) -apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, - std::index_sequence /*unused*/) { - return std::invoke(std::forward(f), std::get(std::forward(t))..., - std::forward(x)); -} - -template -constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { - return details::apply_plus_one_impl( - std::forward(f), std::forward(t), std::forward(x), - std::make_index_sequence< - std::tuple_size_v>>{}); -} - -constexpr auto pointer_range(std::string_view s) noexcept { - return std::tuple(s.data(), s.data() + s.size()); -} - -template -constexpr bool starts_with(std::basic_string_view prefix, - std::basic_string_view s) noexcept { - return s.substr(0, prefix.size()) == prefix; -} - -enum class chars_format { - scientific = 0x1, - fixed = 0x2, - hex = 0x4, - general = fixed | scientific -}; - -struct ConsumeHexPrefixResult { - bool is_hexadecimal; - std::string_view rest; -}; - -using namespace std::literals; - -constexpr auto consume_hex_prefix(std::string_view s) - -> ConsumeHexPrefixResult { - if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { - s.remove_prefix(2); - return {true, s}; - } - return {false, s}; -} - -template -inline auto do_from_chars(std::string_view s) -> T { - T x; - auto [first, last] = pointer_range(s); - auto [ptr, ec] = std::from_chars(first, last, x, Param); - if (ec == std::errc()) { - if (ptr == last) { - return x; - } - throw std::invalid_argument{"pattern does not match to the end"}; - } - if (ec == std::errc::invalid_argument) { - throw std::invalid_argument{"pattern not found"}; - } - if (ec == std::errc::result_out_of_range) { - throw std::range_error{"not representable"}; - } - return x; // unreachable -} - -template struct parse_number { - auto operator()(std::string_view s) -> T { - return do_from_chars(s); - } -}; - -template struct parse_number { - auto operator()(std::string_view s) -> T { - if (auto [ok, rest] = consume_hex_prefix(s); ok) { - return do_from_chars(rest); - } - throw std::invalid_argument{"pattern not found"}; - } -}; - -template struct parse_number { - auto operator()(std::string_view s) -> T { - auto [ok, rest] = consume_hex_prefix(s); - if (ok) { - return do_from_chars(rest); - } - if (starts_with("0"sv, s)) { - return do_from_chars(rest); - } - return do_from_chars(rest); - } -}; - -namespace { - -template inline const auto generic_strtod = nullptr; -template <> inline const auto generic_strtod = strtof; -template <> inline const auto generic_strtod = strtod; -template <> inline const auto generic_strtod = strtold; - -} // namespace - -template inline auto do_strtod(std::string const &s) -> T { - if (isspace(static_cast(s[0])) || s[0] == '+') { - throw std::invalid_argument{"pattern not found"}; - } - - auto [first, last] = pointer_range(s); - char *ptr; - - errno = 0; - auto x = generic_strtod(first, &ptr); - if (errno == 0) { - if (ptr == last) { - return x; - } - throw std::invalid_argument{"pattern does not match to the end"}; - } - if (errno == ERANGE) { - throw std::range_error{"not representable"}; - } - return x; // unreachable -} - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { - throw std::invalid_argument{ - "chars_format::general does not parse hexfloat"}; - } - - return do_strtod(s); - } -}; - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { - throw std::invalid_argument{"chars_format::hex parses hexfloat"}; - } - - return do_strtod(s); - } -}; - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { - throw std::invalid_argument{ - "chars_format::scientific does not parse hexfloat"}; - } - if (s.find_first_of("eE") == std::string::npos) { - throw std::invalid_argument{ - "chars_format::scientific requires exponent part"}; - } - - return do_strtod(s); - } -}; - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { - throw std::invalid_argument{ - "chars_format::fixed does not parse hexfloat"}; - } - if (s.find_first_of("eE") != std::string::npos) { - throw std::invalid_argument{ - "chars_format::fixed does not parse exponent part"}; - } - - return do_strtod(s); - } -}; - -template -std::string join(StrIt first, StrIt last, const std::string &separator) { - if (first == last) { - return ""; - } - std::stringstream value; - value << *first; - ++first; - while (first != last) { - value << separator << *first; - ++first; - } - return value.str(); -} - -} // namespace details - -enum class nargs_pattern { optional, any, at_least_one }; - -enum class default_arguments : unsigned int { - none = 0, - help = 1, - version = 2, - all = help | version, -}; - -inline default_arguments operator&(const default_arguments &a, - const default_arguments &b) { - return static_cast( - static_cast::type>(a) & - static_cast::type>(b)); -} - -class ArgumentParser; - -class Argument { - friend class ArgumentParser; - friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) - -> std::ostream &; - - template - explicit Argument(std::string_view prefix_chars, - std::array &&a, - std::index_sequence /*unused*/) - : m_accepts_optional_like_value(false), - m_is_optional((is_optional(a[I], prefix_chars) || ...)), - m_is_required(false), m_is_repeatable(false), m_is_used(false), - m_prefix_chars(prefix_chars) { - ((void)m_names.emplace_back(a[I]), ...); - std::sort( - m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) { - return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); - }); - } - -public: - template - explicit Argument(std::string_view prefix_chars, - std::array &&a) - : Argument(prefix_chars, std::move(a), std::make_index_sequence{}) {} - - Argument &help(std::string help_text) { - m_help = std::move(help_text); - return *this; - } - - Argument &metavar(std::string metavar) { - m_metavar = std::move(metavar); - return *this; - } - - template Argument &default_value(T &&value) { - m_default_value_repr = details::repr(value); - m_default_value = std::forward(value); - return *this; - } - - Argument &default_value(const char *value) { - return default_value(std::string(value)); - } - - Argument &required() { - m_is_required = true; - return *this; - } - - Argument &implicit_value(std::any value) { - m_implicit_value = std::move(value); - m_num_args_range = NArgsRange{0, 0}; - return *this; - } - - template - auto action(F &&callable, Args &&... bound_args) - -> std::enable_if_t, - Argument &> { - using action_type = std::conditional_t< - std::is_void_v>, - void_action, valued_action>; - if constexpr (sizeof...(Args) == 0) { - m_action.emplace(std::forward(callable)); - } else { - m_action.emplace( - [f = std::forward(callable), - tup = std::make_tuple(std::forward(bound_args)...)]( - std::string const &opt) mutable { - return details::apply_plus_one(f, tup, opt); - }); - } - return *this; - } - - auto &append() { - m_is_repeatable = true; - return *this; - } - - template - auto scan() -> std::enable_if_t, Argument &> { - static_assert(!(std::is_const_v || std::is_volatile_v), - "T should not be cv-qualified"); - auto is_one_of = [](char c, auto... x) constexpr { - return ((c == x) || ...); - }; - - if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'i') && - details::standard_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'u') && - details::standard_unsigned_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'o') && - details::standard_unsigned_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'x', 'X') && - details::standard_unsigned_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'a', 'A') && - std::is_floating_point_v) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'e', 'E') && - std::is_floating_point_v) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'f', 'F') && - std::is_floating_point_v) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'g', 'G') && - std::is_floating_point_v) { - action(details::parse_number()); - } else { - static_assert(alignof(T) == 0, "No scan specification for T"); - } - - return *this; - } - - Argument &nargs(std::size_t num_args) { - m_num_args_range = NArgsRange{num_args, num_args}; - return *this; - } - - Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) { - m_num_args_range = NArgsRange{num_args_min, num_args_max}; - return *this; - } - - Argument &nargs(nargs_pattern pattern) { - switch (pattern) { - case nargs_pattern::optional: - m_num_args_range = NArgsRange{0, 1}; - break; - case nargs_pattern::any: - m_num_args_range = NArgsRange{0, (std::numeric_limits::max)()}; - break; - case nargs_pattern::at_least_one: - m_num_args_range = NArgsRange{1, (std::numeric_limits::max)()}; - break; - } - return *this; - } - - Argument &remaining() { - m_accepts_optional_like_value = true; - return nargs(nargs_pattern::any); - } - - template - Iterator consume(Iterator start, Iterator end, - std::string_view used_name = {}) { - if (!m_is_repeatable && m_is_used) { - throw std::runtime_error("Duplicate argument"); - } - m_is_used = true; - m_used_name = used_name; - - const auto num_args_max = m_num_args_range.get_max(); - const auto num_args_min = m_num_args_range.get_min(); - std::size_t dist = 0; - if (num_args_max == 0) { - m_values.emplace_back(m_implicit_value); - std::visit([](const auto &f) { f({}); }, m_action); - return start; - } - if ((dist = static_cast(std::distance(start, end))) >= - num_args_min) { - if (num_args_max < dist) { - end = std::next(start, static_cast( - num_args_max)); - } - if (!m_accepts_optional_like_value) { - end = std::find_if( - start, end, - std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); - dist = static_cast(std::distance(start, end)); - if (dist < num_args_min) { - throw std::runtime_error("Too few arguments"); - } - } - - struct ActionApply { - void operator()(valued_action &f) { - std::transform(first, last, std::back_inserter(self.m_values), f); - } - - void operator()(void_action &f) { - std::for_each(first, last, f); - if (!self.m_default_value.has_value()) { - if (!self.m_accepts_optional_like_value) { - self.m_values.resize( - static_cast(std::distance(first, last))); - } - } - } - - Iterator first, last; - Argument &self; - }; - std::visit(ActionApply{start, end, *this}, m_action); - return end; - } - if (m_default_value.has_value()) { - return start; - } - throw std::runtime_error("Too few arguments for '" + - std::string(m_used_name) + "'."); - } - - /* - * @throws std::runtime_error if argument values are not valid - */ - void validate() const { - if (m_is_optional) { - // TODO: check if an implicit value was programmed for this argument - if (!m_is_used && !m_default_value.has_value() && m_is_required) { - throw_required_arg_not_used_error(); - } - if (m_is_used && m_is_required && m_values.empty()) { - throw_required_arg_no_value_provided_error(); - } - } else { - if (!m_num_args_range.contains(m_values.size()) && - !m_default_value.has_value()) { - throw_nargs_range_validation_error(); - } - } - } - - std::string get_inline_usage() const { - std::stringstream usage; - // Find the longest variant to show in the usage string - std::string longest_name = m_names.front(); - for (const auto &s : m_names) { - if (s.size() > longest_name.size()) { - longest_name = s; - } - } - if (!m_is_required) { - usage << "["; - } - usage << longest_name; - const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; - if (m_num_args_range.get_max() > 0) { - usage << " " << metavar; - if (m_num_args_range.get_max() > 1) { - usage << "..."; - } - } - if (!m_is_required) { - usage << "]"; - } - return usage.str(); - } - - std::size_t get_arguments_length() const { - - std::size_t names_size = std::accumulate( - std::begin(m_names), std::end(m_names), std::size_t(0), - [](const auto &sum, const auto &s) { return sum + s.size(); }); - - if (is_positional(m_names.front(), m_prefix_chars)) { - // A set metavar means this replaces the names - if (!m_metavar.empty()) { - // Indent and metavar - return 2 + m_metavar.size(); - } - - // Indent and space-separated - return 2 + names_size + (m_names.size() - 1); - } - // Is an option - include both names _and_ metavar - // size = text + (", " between names) - std::size_t size = names_size + 2 * (m_names.size() - 1); - if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) { - size += m_metavar.size() + 1; - } - return size + 2; // indent - } - - friend std::ostream &operator<<(std::ostream &stream, - const Argument &argument) { - std::stringstream name_stream; - name_stream << " "; // indent - if (argument.is_positional(argument.m_names.front(), - argument.m_prefix_chars)) { - if (!argument.m_metavar.empty()) { - name_stream << argument.m_metavar; - } else { - name_stream << details::join(argument.m_names.begin(), - argument.m_names.end(), " "); - } - } else { - name_stream << details::join(argument.m_names.begin(), - argument.m_names.end(), ", "); - // If we have a metavar, and one narg - print the metavar - if (!argument.m_metavar.empty() && - argument.m_num_args_range == NArgsRange{1, 1}) { - name_stream << " " << argument.m_metavar; - } - } - stream << name_stream.str() << "\t" << argument.m_help; - - // print nargs spec - if (!argument.m_help.empty()) { - stream << " "; - } - stream << argument.m_num_args_range; - - if (argument.m_default_value.has_value() && - argument.m_num_args_range != NArgsRange{0, 0}) { - stream << "[default: " << argument.m_default_value_repr << "]"; - } else if (argument.m_is_required) { - stream << "[required]"; - } - stream << "\n"; - return stream; - } - - template bool operator!=(const T &rhs) const { - return !(*this == rhs); - } - - /* - * Compare to an argument value of known type - * @throws std::logic_error in case of incompatible types - */ - template bool operator==(const T &rhs) const { - if constexpr (!details::IsContainer) { - return get() == rhs; - } else { - using ValueType = typename T::value_type; - auto lhs = get(); - return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), - std::end(rhs), - [](const auto &a, const auto &b) { - return std::any_cast(a) == b; - }); - } - } - -private: - class NArgsRange { - std::size_t m_min; - std::size_t m_max; - - public: - NArgsRange(std::size_t minimum, std::size_t maximum) - : m_min(minimum), m_max(maximum) { - if (minimum > maximum) { - throw std::logic_error("Range of number of arguments is invalid"); - } - } - - bool contains(std::size_t value) const { - return value >= m_min && value <= m_max; - } - - bool is_exact() const { return m_min == m_max; } - - bool is_right_bounded() const { - return m_max < (std::numeric_limits::max)(); - } - - std::size_t get_min() const { return m_min; } - - std::size_t get_max() const { return m_max; } - - // Print help message - friend auto operator<<(std::ostream &stream, const NArgsRange &range) - -> std::ostream & { - if (range.m_min == range.m_max) { - if (range.m_min != 0 && range.m_min != 1) { - stream << "[nargs: " << range.m_min << "] "; - } - } else { - if (range.m_max == (std::numeric_limits::max)()) { - stream << "[nargs: " << range.m_min << " or more] "; - } else { - stream << "[nargs=" << range.m_min << ".." << range.m_max << "] "; - } - } - return stream; - } - - bool operator==(const NArgsRange &rhs) const { - return rhs.m_min == m_min && rhs.m_max == m_max; - } - - bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); } - }; - - void throw_nargs_range_validation_error() const { - std::stringstream stream; - if (!m_used_name.empty()) { - stream << m_used_name << ": "; - } else { - stream << m_names.front() << ": "; - } - if (m_num_args_range.is_exact()) { - stream << m_num_args_range.get_min(); - } else if (m_num_args_range.is_right_bounded()) { - stream << m_num_args_range.get_min() << " to " - << m_num_args_range.get_max(); - } else { - stream << m_num_args_range.get_min() << " or more"; - } - stream << " argument(s) expected. " << m_values.size() << " provided."; - throw std::runtime_error(stream.str()); - } - - void throw_required_arg_not_used_error() const { - std::stringstream stream; - stream << m_names.front() << ": required."; - throw std::runtime_error(stream.str()); - } - - void throw_required_arg_no_value_provided_error() const { - std::stringstream stream; - stream << m_used_name << ": no value provided."; - throw std::runtime_error(stream.str()); - } - - static constexpr int eof = std::char_traits::eof(); - - static auto lookahead(std::string_view s) -> int { - if (s.empty()) { - return eof; - } - return static_cast(static_cast(s[0])); - } - - /* - * decimal-literal: - * '0' - * nonzero-digit digit-sequence_opt - * integer-part fractional-part - * fractional-part - * integer-part '.' exponent-part_opt - * integer-part exponent-part - * - * integer-part: - * digit-sequence - * - * fractional-part: - * '.' post-decimal-point - * - * post-decimal-point: - * digit-sequence exponent-part_opt - * - * exponent-part: - * 'e' post-e - * 'E' post-e - * - * post-e: - * sign_opt digit-sequence - * - * sign: one of - * '+' '-' - */ - static bool is_decimal_literal(std::string_view s) { - auto is_digit = [](auto c) constexpr { - switch (c) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return true; - default: - return false; - } - }; - - // precondition: we have consumed or will consume at least one digit - auto consume_digits = [=](std::string_view sd) { - // NOLINTNEXTLINE(readability-qualified-auto) - auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit); - return sd.substr(static_cast(it - std::begin(sd))); - }; - - switch (lookahead(s)) { - case '0': { - s.remove_prefix(1); - if (s.empty()) { - return true; - } - goto integer_part; - } - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - s = consume_digits(s); - if (s.empty()) { - return true; - } - goto integer_part_consumed; - } - case '.': { - s.remove_prefix(1); - goto post_decimal_point; - } - default: - return false; - } - - integer_part: - s = consume_digits(s); - integer_part_consumed: - switch (lookahead(s)) { - case '.': { - s.remove_prefix(1); - if (is_digit(lookahead(s))) { - goto post_decimal_point; - } else { - goto exponent_part_opt; - } - } - case 'e': - case 'E': { - s.remove_prefix(1); - goto post_e; - } - default: - return false; - } - - post_decimal_point: - if (is_digit(lookahead(s))) { - s = consume_digits(s); - goto exponent_part_opt; - } - return false; - - exponent_part_opt: - switch (lookahead(s)) { - case eof: - return true; - case 'e': - case 'E': { - s.remove_prefix(1); - goto post_e; - } - default: - return false; - } - - post_e: - switch (lookahead(s)) { - case '-': - case '+': - s.remove_prefix(1); - } - if (is_digit(lookahead(s))) { - s = consume_digits(s); - return s.empty(); - } - return false; - } - - static bool is_optional(std::string_view name, - std::string_view prefix_chars) { - return !is_positional(name, prefix_chars); - } - - /* - * positional: - * _empty_ - * '-' - * '-' decimal-literal - * !'-' anything - */ - static bool is_positional(std::string_view name, - std::string_view prefix_chars) { - auto first = lookahead(name); - - if (first == eof) { - return true; - } else if (prefix_chars.find(static_cast(first)) != - std::string_view::npos) { - name.remove_prefix(1); - if (name.empty()) { - return true; - } - return is_decimal_literal(name); - } - return true; - } - - /* - * Get argument value given a type - * @throws std::logic_error in case of incompatible types - */ - template T get() const { - if (!m_values.empty()) { - if constexpr (details::IsContainer) { - return any_cast_container(m_values); - } else { - return std::any_cast(m_values.front()); - } - } - if (m_default_value.has_value()) { - return std::any_cast(m_default_value); - } - if constexpr (details::IsContainer) { - if (!m_accepts_optional_like_value) { - return any_cast_container(m_values); - } - } - - throw std::logic_error("No value provided for '" + m_names.back() + "'."); - } - - /* - * Get argument value given a type. - * @pre The object has no default value. - * @returns The stored value if any, std::nullopt otherwise. - */ - template auto present() const -> std::optional { - if (m_default_value.has_value()) { - throw std::logic_error("Argument with default value always presents"); - } - if (m_values.empty()) { - return std::nullopt; - } - if constexpr (details::IsContainer) { - return any_cast_container(m_values); - } - return std::any_cast(m_values.front()); - } - - template - static auto any_cast_container(const std::vector &operand) -> T { - using ValueType = typename T::value_type; - - T result; - std::transform( - std::begin(operand), std::end(operand), std::back_inserter(result), - [](const auto &value) { return std::any_cast(value); }); - return result; - } - - std::vector m_names; - std::string_view m_used_name; - std::string m_help; - std::string m_metavar; - std::any m_default_value; - std::string m_default_value_repr; - std::any m_implicit_value; - using valued_action = std::function; - using void_action = std::function; - std::variant m_action{ - std::in_place_type, - [](const std::string &value) { return value; }}; - std::vector m_values; - NArgsRange m_num_args_range{1, 1}; - // Bit field of bool values. Set default value in ctor. - bool m_accepts_optional_like_value : 1; - bool m_is_optional : 1; - bool m_is_required : 1; - bool m_is_repeatable : 1; - bool m_is_used : 1; - std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars -}; - -class ArgumentParser { -public: - explicit ArgumentParser(std::string program_name = {}, - std::string version = "1.0", - default_arguments add_args = default_arguments::all, - bool exit_on_default_arguments = true) - : m_program_name(std::move(program_name)), m_version(std::move(version)), - m_exit_on_default_arguments(exit_on_default_arguments), - m_parser_path(m_program_name) { - if ((add_args & default_arguments::help) == default_arguments::help) { - add_argument("-h", "--help") - .action([&](const auto & /*unused*/) { - std::cout << help().str(); - if (m_exit_on_default_arguments) { - std::exit(0); - } - }) - .default_value(false) - .help("shows help message and exits") - .implicit_value(true) - .nargs(0); - } - if ((add_args & default_arguments::version) == default_arguments::version) { - add_argument("-v", "--version") - .action([&](const auto & /*unused*/) { - std::cout << m_version << std::endl; - if (m_exit_on_default_arguments) { - std::exit(0); - } - }) - .default_value(false) - .help("prints version information and exits") - .implicit_value(true) - .nargs(0); - } - } - - ArgumentParser(ArgumentParser &&) noexcept = default; - ArgumentParser &operator=(ArgumentParser &&) = default; - - ArgumentParser(const ArgumentParser &other) - : m_program_name(other.m_program_name), m_version(other.m_version), - m_description(other.m_description), m_epilog(other.m_epilog), - m_prefix_chars(other.m_prefix_chars), - m_assign_chars(other.m_assign_chars), m_is_parsed(other.m_is_parsed), - m_positional_arguments(other.m_positional_arguments), - m_optional_arguments(other.m_optional_arguments), - m_parser_path(other.m_parser_path), m_subparsers(other.m_subparsers) { - for (auto it = std::begin(m_positional_arguments); - it != std::end(m_positional_arguments); ++it) { - index_argument(it); - } - for (auto it = std::begin(m_optional_arguments); - it != std::end(m_optional_arguments); ++it) { - index_argument(it); - } - for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers); - ++it) { - m_subparser_map.insert_or_assign(it->get().m_program_name, it); - m_subparser_used.insert_or_assign(it->get().m_program_name, false); - } - } - - ~ArgumentParser() = default; - - ArgumentParser &operator=(const ArgumentParser &other) { - auto tmp = other; - std::swap(*this, tmp); - return *this; - } - - explicit operator bool() const { - auto arg_used = std::any_of(m_argument_map.cbegin(), - m_argument_map.cend(), - [](auto &it) { - return it.second->m_is_used; - }); - auto subparser_used = std::any_of(m_subparser_used.cbegin(), - m_subparser_used.cend(), - [](auto &it) { - return it.second; - }); - - return m_is_parsed && (arg_used || subparser_used); - } - - // Parameter packing - // Call add_argument with variadic number of string arguments - template Argument &add_argument(Targs... f_args) { - using array_of_sv = std::array; - auto argument = - m_optional_arguments.emplace(std::cend(m_optional_arguments), - m_prefix_chars, array_of_sv{f_args...}); - - if (!argument->m_is_optional) { - m_positional_arguments.splice(std::cend(m_positional_arguments), - m_optional_arguments, argument); - } - - index_argument(argument); - return *argument; - } - - // Parameter packed add_parents method - // Accepts a variadic number of ArgumentParser objects - template - ArgumentParser &add_parents(const Targs &... f_args) { - for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { - for (const auto &argument : parent_parser.m_positional_arguments) { - auto it = m_positional_arguments.insert( - std::cend(m_positional_arguments), argument); - index_argument(it); - } - for (const auto &argument : parent_parser.m_optional_arguments) { - auto it = m_optional_arguments.insert(std::cend(m_optional_arguments), - argument); - index_argument(it); - } - } - return *this; - } - - ArgumentParser &add_description(std::string description) { - m_description = std::move(description); - return *this; - } - - ArgumentParser &add_epilog(std::string epilog) { - m_epilog = std::move(epilog); - return *this; - } - - /* Getter for arguments and subparsers. - * @throws std::logic_error in case of an invalid argument or subparser name - */ - template - T& at(std::string_view name) { - if constexpr (std::is_same_v) { - return (*this)[name]; - } else { - auto subparser_it = m_subparser_map.find(name); - if (subparser_it != m_subparser_map.end()) { - return subparser_it->second->get(); - } - throw std::logic_error("No such subparser: " + std::string(name)); - } - } - - ArgumentParser &set_prefix_chars(std::string prefix_chars) { - m_prefix_chars = std::move(prefix_chars); - return *this; - } - - ArgumentParser &set_assign_chars(std::string assign_chars) { - m_assign_chars = std::move(assign_chars); - return *this; - } - - /* Call parse_args_internal - which does all the work - * Then, validate the parsed arguments - * This variant is used mainly for testing - * @throws std::runtime_error in case of any invalid argument - */ - void parse_args(const std::vector &arguments) { - parse_args_internal(arguments); - // Check if all arguments are parsed - for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - argument->validate(); - } - } - - /* Call parse_known_args_internal - which does all the work - * Then, validate the parsed arguments - * This variant is used mainly for testing - * @throws std::runtime_error in case of any invalid argument - */ - std::vector - parse_known_args(const std::vector &arguments) { - auto unknown_arguments = parse_known_args_internal(arguments); - // Check if all arguments are parsed - for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - argument->validate(); - } - return unknown_arguments; - } - - /* Main entry point for parsing command-line arguments using this - * ArgumentParser - * @throws std::runtime_error in case of any invalid argument - */ - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - void parse_args(int argc, const char *const argv[]) { - parse_args({argv, argv + argc}); - } - - /* Main entry point for parsing command-line arguments using this - * ArgumentParser - * @throws std::runtime_error in case of any invalid argument - */ - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - auto parse_known_args(int argc, const char *const argv[]) { - return parse_known_args({argv, argv + argc}); - } - - /* Getter for options with default values. - * @throws std::logic_error if parse_args() has not been previously called - * @throws std::logic_error if there is no such option - * @throws std::logic_error if the option has no value - * @throws std::bad_any_cast if the option is not of type T - */ - template T get(std::string_view arg_name) const { - if (!m_is_parsed) { - throw std::logic_error("Nothing parsed, no arguments are available."); - } - return (*this)[arg_name].get(); - } - - /* Getter for options without default values. - * @pre The option has no default value. - * @throws std::logic_error if there is no such option - * @throws std::bad_any_cast if the option is not of type T - */ - template - auto present(std::string_view arg_name) const -> std::optional { - return (*this)[arg_name].present(); - } - - /* Getter that returns true for user-supplied options. Returns false if not - * user-supplied, even with a default value. - */ - auto is_used(std::string_view arg_name) const { - return (*this)[arg_name].m_is_used; - } - - /* Getter that returns true if a subcommand is used. - */ - auto is_subcommand_used(std::string_view subcommand_name) const { - return m_subparser_used.at(subcommand_name); - } - - /* Getter that returns true if a subcommand is used. - */ - auto is_subcommand_used(const ArgumentParser &subparser) const { - return is_subcommand_used(subparser.m_program_name); - } - - /* Indexing operator. Return a reference to an Argument object - * Used in conjuction with Argument.operator== e.g., parser["foo"] == true - * @throws std::logic_error in case of an invalid argument name - */ - Argument &operator[](std::string_view arg_name) const { - auto it = m_argument_map.find(arg_name); - if (it != m_argument_map.end()) { - return *(it->second); - } - if (!is_valid_prefix_char(arg_name.front())) { - std::string name(arg_name); - const auto legal_prefix_char = get_any_valid_prefix_char(); - const auto prefix = std::string(1, legal_prefix_char); - - // "-" + arg_name - name = prefix + name; - it = m_argument_map.find(name); - if (it != m_argument_map.end()) { - return *(it->second); - } - // "--" + arg_name - name = prefix + name; - it = m_argument_map.find(name); - if (it != m_argument_map.end()) { - return *(it->second); - } - } - throw std::logic_error("No such argument: " + std::string(arg_name)); - } - - // Print help message - friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) - -> std::ostream & { - stream.setf(std::ios_base::left); - - auto longest_arg_length = parser.get_length_of_longest_argument(); - - stream << parser.usage() << "\n\n"; - - if (!parser.m_description.empty()) { - stream << parser.m_description << "\n\n"; - } - - if (!parser.m_positional_arguments.empty()) { - stream << "Positional arguments:\n"; - } - - for (const auto &argument : parser.m_positional_arguments) { - stream.width(static_cast(longest_arg_length)); - stream << argument; - } - - if (!parser.m_optional_arguments.empty()) { - stream << (parser.m_positional_arguments.empty() ? "" : "\n") - << "Optional arguments:\n"; - } - - for (const auto &argument : parser.m_optional_arguments) { - stream.width(static_cast(longest_arg_length)); - stream << argument; - } - - if (!parser.m_subparser_map.empty()) { - stream << (parser.m_positional_arguments.empty() - ? (parser.m_optional_arguments.empty() ? "" : "\n") - : "\n") - << "Subcommands:\n"; - for (const auto &[command, subparser] : parser.m_subparser_map) { - stream << std::setw(2) << " "; - stream << std::setw(static_cast(longest_arg_length - 2)) - << command; - stream << " " << subparser->get().m_description << "\n"; - } - } - - if (!parser.m_epilog.empty()) { - stream << '\n'; - stream << parser.m_epilog << "\n\n"; - } - - return stream; - } - - // Format help message - auto help() const -> std::stringstream { - std::stringstream out; - out << *this; - return out; - } - - // Format usage part of help only - auto usage() const -> std::string { - std::stringstream stream; - - stream << "Usage: " << this->m_program_name; - - // Add any options inline here - for (const auto &argument : this->m_optional_arguments) { - stream << " " << argument.get_inline_usage(); - } - // Put positional arguments after the optionals - for (const auto &argument : this->m_positional_arguments) { - if (!argument.m_metavar.empty()) { - stream << " " << argument.m_metavar; - } else { - stream << " " << argument.m_names.front(); - } - } - // Put subcommands after positional arguments - if (!m_subparser_map.empty()) { - stream << " {"; - std::size_t i{0}; - for (const auto &[command, unused] : m_subparser_map) { - if (i == 0) { - stream << command; - } else { - stream << "," << command; - } - ++i; - } - stream << "}"; - } - - return stream.str(); - } - - // Printing the one and only help message - // I've stuck with a simple message format, nothing fancy. - [[deprecated("Use cout << program; instead. See also help().")]] std::string - print_help() const { - auto out = help(); - std::cout << out.rdbuf(); - return out.str(); - } - - void add_subparser(ArgumentParser &parser) { - parser.m_parser_path = m_program_name + " " + parser.m_program_name; - auto it = m_subparsers.emplace(std::cend(m_subparsers), parser); - m_subparser_map.insert_or_assign(parser.m_program_name, it); - m_subparser_used.insert_or_assign(parser.m_program_name, false); - } - -private: - bool is_valid_prefix_char(char c) const { - return m_prefix_chars.find(c) != std::string::npos; - } - - char get_any_valid_prefix_char() const { return m_prefix_chars[0]; } - - /* - * Pre-process this argument list. Anything starting with "--", that - * contains an =, where the prefix before the = has an entry in the - * options table, should be split. - */ - std::vector - preprocess_arguments(const std::vector &raw_arguments) const { - std::vector arguments{}; - for (const auto &arg : raw_arguments) { - - const auto argument_starts_with_prefix_chars = - [this](const std::string &a) -> bool { - if (!a.empty()) { - - const auto legal_prefix = [this](char c) -> bool { - return m_prefix_chars.find(c) != std::string::npos; - }; - - // Windows-style - // if '/' is a legal prefix char - // then allow single '/' followed by argument name, followed by an - // assign char, e.g., ':' e.g., 'test.exe /A:Foo' - const auto windows_style = legal_prefix('/'); - - if (windows_style) { - if (legal_prefix(a[0])) { - return true; - } - } else { - // Slash '/' is not a legal prefix char - // For all other characters, only support long arguments - // i.e., the argument must start with 2 prefix chars, e.g, - // '--foo' e,g, './test --foo=Bar -DARG=yes' - if (a.size() > 1) { - return (legal_prefix(a[0]) && legal_prefix(a[1])); - } - } - } - return false; - }; - - // Check that: - // - We don't have an argument named exactly this - // - The argument starts with a prefix char, e.g., "--" - // - The argument contains an assign char, e.g., "=" - auto assign_char_pos = arg.find_first_of(m_assign_chars); - - if (m_argument_map.find(arg) == m_argument_map.end() && - argument_starts_with_prefix_chars(arg) && - assign_char_pos != std::string::npos) { - // Get the name of the potential option, and check it exists - std::string opt_name = arg.substr(0, assign_char_pos); - if (m_argument_map.find(opt_name) != m_argument_map.end()) { - // This is the name of an option! Split it into two parts - arguments.push_back(std::move(opt_name)); - arguments.push_back(arg.substr(assign_char_pos + 1)); - continue; - } - } - // If we've fallen through to here, then it's a standard argument - arguments.push_back(arg); - } - return arguments; - } - - /* - * @throws std::runtime_error in case of any invalid argument - */ - void parse_args_internal(const std::vector &raw_arguments) { - auto arguments = preprocess_arguments(raw_arguments); - if (m_program_name.empty() && !arguments.empty()) { - m_program_name = arguments.front(); - } - auto end = std::end(arguments); - auto positional_argument_it = std::begin(m_positional_arguments); - for (auto it = std::next(std::begin(arguments)); it != end;) { - const auto ¤t_argument = *it; - if (Argument::is_positional(current_argument, m_prefix_chars)) { - if (positional_argument_it == std::end(m_positional_arguments)) { - - std::string_view maybe_command = current_argument; - - // Check sub-parsers - auto subparser_it = m_subparser_map.find(maybe_command); - if (subparser_it != m_subparser_map.end()) { - - // build list of remaining args - const auto unprocessed_arguments = - std::vector(it, end); - - // invoke subparser - m_is_parsed = true; - m_subparser_used[maybe_command] = true; - return subparser_it->second->get().parse_args( - unprocessed_arguments); - } - - throw std::runtime_error( - "Maximum number of positional arguments exceeded"); - } - auto argument = positional_argument_it++; - it = argument->consume(it, end); - continue; - } - - auto arg_map_it = m_argument_map.find(current_argument); - if (arg_map_it != m_argument_map.end()) { - auto argument = arg_map_it->second; - it = argument->consume(std::next(it), end, arg_map_it->first); - } else if (const auto &compound_arg = current_argument; - compound_arg.size() > 1 && - is_valid_prefix_char(compound_arg[0]) && - !is_valid_prefix_char(compound_arg[1])) { - ++it; - for (std::size_t j = 1; j < compound_arg.size(); j++) { - auto hypothetical_arg = std::string{'-', compound_arg[j]}; - auto arg_map_it2 = m_argument_map.find(hypothetical_arg); - if (arg_map_it2 != m_argument_map.end()) { - auto argument = arg_map_it2->second; - it = argument->consume(it, end, arg_map_it2->first); - } else { - throw std::runtime_error("Unknown argument: " + current_argument); - } - } - } else { - throw std::runtime_error("Unknown argument: " + current_argument); - } - } - m_is_parsed = true; - } - - /* - * Like parse_args_internal but collects unused args into a vector - */ - std::vector - parse_known_args_internal(const std::vector &raw_arguments) { - auto arguments = preprocess_arguments(raw_arguments); - - std::vector unknown_arguments{}; - - if (m_program_name.empty() && !arguments.empty()) { - m_program_name = arguments.front(); - } - auto end = std::end(arguments); - auto positional_argument_it = std::begin(m_positional_arguments); - for (auto it = std::next(std::begin(arguments)); it != end;) { - const auto ¤t_argument = *it; - if (Argument::is_positional(current_argument, m_prefix_chars)) { - if (positional_argument_it == std::end(m_positional_arguments)) { - - std::string_view maybe_command = current_argument; - - // Check sub-parsers - auto subparser_it = m_subparser_map.find(maybe_command); - if (subparser_it != m_subparser_map.end()) { - - // build list of remaining args - const auto unprocessed_arguments = - std::vector(it, end); - - // invoke subparser - m_is_parsed = true; - m_subparser_used[maybe_command] = true; - return subparser_it->second->get().parse_known_args_internal( - unprocessed_arguments); - } - - // save current argument as unknown and go to next argument - unknown_arguments.push_back(current_argument); - ++it; - } else { - // current argument is the value of a positional argument - // consume it - auto argument = positional_argument_it++; - it = argument->consume(it, end); - } - continue; - } - - auto arg_map_it = m_argument_map.find(current_argument); - if (arg_map_it != m_argument_map.end()) { - auto argument = arg_map_it->second; - it = argument->consume(std::next(it), end, arg_map_it->first); - } else if (const auto &compound_arg = current_argument; - compound_arg.size() > 1 && - is_valid_prefix_char(compound_arg[0]) && - !is_valid_prefix_char(compound_arg[1])) { - ++it; - for (std::size_t j = 1; j < compound_arg.size(); j++) { - auto hypothetical_arg = std::string{'-', compound_arg[j]}; - auto arg_map_it2 = m_argument_map.find(hypothetical_arg); - if (arg_map_it2 != m_argument_map.end()) { - auto argument = arg_map_it2->second; - it = argument->consume(it, end, arg_map_it2->first); - } else { - unknown_arguments.push_back(current_argument); - break; - } - } - } else { - // current argument is an optional-like argument that is unknown - // save it and move to next argument - unknown_arguments.push_back(current_argument); - ++it; - } - } - m_is_parsed = true; - return unknown_arguments; - } - - // Used by print_help. - std::size_t get_length_of_longest_argument() const { - if (m_argument_map.empty()) { - return 0; - } - std::size_t max_size = 0; - for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - max_size = std::max(max_size, argument->get_arguments_length()); - } - for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) { - max_size = std::max(max_size, command.size()); - } - return max_size; - } - - using argument_it = std::list::iterator; - using argument_parser_it = - std::list>::iterator; - - void index_argument(argument_it it) { - for (const auto &name : std::as_const(it->m_names)) { - m_argument_map.insert_or_assign(name, it); - } - } - - std::string m_program_name; - std::string m_version; - std::string m_description; - std::string m_epilog; - bool m_exit_on_default_arguments = true; - std::string m_prefix_chars{"-"}; - std::string m_assign_chars{"="}; - bool m_is_parsed = false; - std::list m_positional_arguments; - std::list m_optional_arguments; - std::map m_argument_map; - std::string m_parser_path; - std::list> m_subparsers; - std::map m_subparser_map; - std::map m_subparser_used; -}; - -} // namespace argparse diff --git a/src/CLI/include/argument_exception.hpp b/src/CLI/include/argument_exception.hpp deleted file mode 100644 index 4627b0b11..000000000 --- a/src/CLI/include/argument_exception.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ARGUMENT_EXCEPTION -#define ARGUMENT_EXCEPTION - -#include -#include - -class ArgumentException: public std::exception { -private: -public: - std::string message; - ArgumentException(std::string msg) : message(msg) { - this->message = msg; - } -}; -#endif \ No newline at end of file diff --git a/src/CLI/include/backend.hpp b/src/CLI/include/backend.hpp deleted file mode 100644 index 4e755cb65..000000000 --- a/src/CLI/include/backend.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef BACKEND_GUARD -#define BACKEND_GUARD - -#include -#include -#include -#include -#include -#include "argparse/argparse.hpp" -#include "argument_exception.hpp" - - -/** - * Check to see if a file exists on the system. - * Technically this will return false if it exists but you don't have permission. -*/ -inline bool file_exists(std::string fname) { - std::fstream f(fname.c_str()); - return f.good(); -} - -inline std::string absolute_fp(std::string fname) { - return std::string(std::filesystem::absolute(fname)); -} - -inline bool can_exec(const std::filesystem::path& fp) { - auto entry = std::filesystem::directory_entry(fp); - return ( - entry.exists() - && - std::filesystem::perms::none != (entry.status().permissions() & std::filesystem::perms::owner_exec) - ); -} - -static std::vector& get_paths() { - std::string path = std::getenv("PATH"); - static std::vector result; - if (result.size() > 0) return result; - result.push_back(std::filesystem::canonical("/proc/self/exe").parent_path()); - result.push_back(std::filesystem::path(".")); - - size_t i = 0; - // Windows uses ";" as a delimeter, but we don't support windows yet. - while ((i = path.find(":")) != std::string::npos) { - result.push_back(std::filesystem::path(path.substr(0, i))); - path.erase(0, i + 1); - } - return result; -} - -struct FierroBackend { - std::string name = ""; - std::shared_ptr command; - std::optional exec_path{}; - FierroBackend(std::string name): name(name) { - exec_path = find(); - } - - std::optional find() { - return this->find(get_paths()); - } - - std::optional find(const std::vector& paths) { - for(const auto & path : paths) { - auto potential_path = path / this->name; - if (can_exec(potential_path)) return {potential_path}; - } - return {}; - } - - virtual int invoke() = 0; -}; -#endif \ No newline at end of file diff --git a/src/CLI/src/main.cpp b/src/CLI/src/main.cpp deleted file mode 100644 index c7cd3d018..000000000 --- a/src/CLI/src/main.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "backend.hpp" -#include "FierroParallelBackends.hpp" -#include "MeshBuilderBackend.hpp" -#include "VoxelizerBackend.hpp" -#include "argparse/argparse.hpp" -#include "argument_exception.hpp" -#include -#include -#include -#include -#include - -std::vector> BACKENDS { - std::make_shared(), - std::make_shared(), - std::make_shared(), - std::make_shared(), -}; - -std::vector> find_fierro_backends() { - auto found = std::vector>(); - for(auto backend : BACKENDS) { - if(backend->exec_path.has_value()) found.push_back(backend); - } - - return found; -} - -/** - * Execute a function with a particular working directory. - * - * @param dir The working directory to execute the function in. - * If the directoy doesn't exist, it will be created. - * Will also create parent directories if necessary. - * @param f The function to execute. - * - * @return The return value of `f` if there is one. Else void. -*/ -template -T with_curdir(std::string dir, std::function f) { - T val; - with_curdir(dir, [&]() -> void { val = f(); }); - return val; -} - -/** - * `with_curdir` template specialization for void functions. -*/ -template<> -void with_curdir(std::string dir, std::function f) { - auto old_path = std::filesystem::current_path(); - - dir = std::filesystem::absolute(dir); - if (!std::filesystem::exists(dir)) std::filesystem::create_directories(dir); - - std::filesystem::current_path(dir); - f(); - std::filesystem::current_path(old_path); -} - -int main(int argc, char** argv) { - argparse::ArgumentParser parser("fierro"); - parser.add_description(""); - parser.add_argument("-o", "--output") - .help("output directory for the program") - .default_value("."); - - auto backends = find_fierro_backends(); - for(auto backend : backends) { - parser.add_subparser(*backend->command); - } - - try { - parser.parse_args(argc, argv); - } catch(const std::runtime_error& e) { - std::cerr << e.what() << std::endl; - std::cerr << parser; - std::exit(1); - } - - for(auto backend : backends) { - if (parser.is_subcommand_used(*backend->command)) { - return with_curdir( - parser.get("output"), - [&]() { - try { - return backend->invoke(); - } catch (ArgumentException e) { - std::cerr << e.message << std::endl; - std::cerr << parser; - std::exit(1); - } - } - ); - } - } - - // If they didn't select anything, give them some help. - std::cout << parser << std::endl; -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8758ebda9..78e716178 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,7 +12,7 @@ endif() add_subdirectory(Yaml-Serializable) add_subdirectory(Mesh-Builder) add_subdirectory(Voxelizer) -add_subdirectory(CLI) + if(BUILD_IMPLICIT_SOLVER OR BUILD_PARALLEL_EXPLICIT_SOLVER) add_subdirectory(Parallel-Solvers)